├── release-notes.txt ├── lib ├── Clipper │ ├── README │ ├── clipper_library.dll │ └── License.txt ├── Piwik.Tracker │ └── Piwik.Tracker.dll └── SoundCloud.API.Clent │ └── SoundCloud.API.Client.dll ├── .paket ├── paket.bootstrapper.exe └── paket.targets ├── src └── TurntNinja │ ├── Content │ ├── Images │ │ ├── icon.icns │ │ ├── icon.ico │ │ └── DefaultSkin.png │ ├── Songs │ │ ├── Wayfarer.mp3 │ │ ├── Never-Stop.mp3 │ │ └── song attribution.txt │ ├── Fonts │ │ ├── Oswald-Bold.ttf │ │ ├── Oswald-Light.ttf │ │ ├── Oswald-Regular.ttf │ │ ├── Open Sans │ │ │ ├── OpenSans-Bold.ttf │ │ │ ├── OpenSans-Light.ttf │ │ │ ├── OpenSans-Italic.ttf │ │ │ ├── OpenSans-Regular.ttf │ │ │ ├── OpenSans-Semibold.ttf │ │ │ ├── OpenSans-BoldItalic.ttf │ │ │ ├── OpenSans-ExtraBold.ttf │ │ │ ├── OpenSans-LightItalic.ttf │ │ │ ├── OpenSans-ExtraBoldItalic.ttf │ │ │ └── OpenSans-SemiboldItalic.ttf │ │ ├── Ostrich Sans │ │ │ ├── OstrichSans-Black.otf │ │ │ └── Open Font License.markdown │ │ └── OFL.txt │ └── Shaders │ │ ├── simple.fs │ │ ├── simple.vs │ │ └── colour.vs │ ├── CSCore.dll.config │ ├── Audio │ ├── SongCache.cs │ ├── IAudioAnalyser.cs │ ├── PlaylistHelper.cs │ ├── SongBase.cs │ ├── Song.cs │ ├── AcoustIDCSCore.cs │ └── AudioFeatures.cs │ ├── paket.references │ ├── FileSystem │ ├── FileBrowserEntry.cs │ ├── IFileSystem.cs │ ├── RecentFileSystem.cs │ ├── SoundCloudFileSystem.cs │ └── LocalFileSystem.cs │ ├── TurntNinja.nuspec │ ├── Game │ ├── HighScore.cs │ ├── Difficulty.cs │ ├── OnsetCollection.cs │ ├── Player.cs │ └── StageAudio.cs │ ├── Settings.cs │ ├── OpenTK.dll.config │ ├── Properties │ ├── AssemblyInfo.cs │ └── Settings.settings │ ├── Core │ ├── HUSLColor.cs │ └── Settings │ │ ├── PropertySettings.cs │ │ └── JsonSettings.cs │ ├── app.manifest │ ├── Logging │ ├── SentryErrorReporting.cs │ └── PiwikAnalytics.cs │ ├── App.config │ ├── GUI │ ├── FirstRunScene.cs │ ├── ChooseSongScene.cs │ ├── GameScene.cs │ ├── EndGameScene.cs │ ├── LoadingScene.cs │ └── MenuScene.cs │ ├── Program.cs │ └── Generation │ └── StageGeometryBuilder.cs ├── appveyor.yml ├── turnt-ninja.sln.DotSettings ├── README.md ├── scripts ├── package-files.ps1 ├── package-files-new.ps1 ├── create-squirrel-package.ps1 └── Butler.fs ├── .travis.yml ├── docs ├── licenses │ ├── QuickFont.txt │ ├── ColorMine.txt │ ├── Substructio.txt │ ├── Clipper.txt │ ├── OpenTK.txt │ └── CSCore.md └── souncloud-api-v2-notes.md ├── paket.dependencies ├── turnt-ninja.sln ├── paket.lock ├── .gitattributes └── .gitignore /release-notes.txt: -------------------------------------------------------------------------------- 1 | Release notes go here -------------------------------------------------------------------------------- /lib/Clipper/README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/lib/Clipper/README -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /lib/Clipper/clipper_library.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/lib/Clipper/clipper_library.dll -------------------------------------------------------------------------------- /lib/Piwik.Tracker/Piwik.Tracker.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/lib/Piwik.Tracker/Piwik.Tracker.dll -------------------------------------------------------------------------------- /src/TurntNinja/Content/Images/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Images/icon.icns -------------------------------------------------------------------------------- /src/TurntNinja/Content/Images/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Images/icon.ico -------------------------------------------------------------------------------- /src/TurntNinja/Content/Songs/Wayfarer.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Songs/Wayfarer.mp3 -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Oswald-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Oswald-Bold.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Oswald-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Oswald-Light.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Images/DefaultSkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Images/DefaultSkin.png -------------------------------------------------------------------------------- /src/TurntNinja/Content/Songs/Never-Stop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Songs/Never-Stop.mp3 -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Oswald-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Oswald-Regular.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Shaders/simple.fs: -------------------------------------------------------------------------------- 1 | 2 | in vec4 vs_color; 3 | out vec4 color; 4 | 5 | void main(void) 6 | { 7 | color = vs_color; 8 | } 9 | -------------------------------------------------------------------------------- /lib/SoundCloud.API.Clent/SoundCloud.API.Client.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/lib/SoundCloud.API.Clent/SoundCloud.API.Client.dll -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Light.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-Semibold.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-BoldItalic.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-LightItalic.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Ostrich Sans/OstrichSans-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Ostrich Sans/OstrichSans-Black.otf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Open Sans/OpenSans-SemiboldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opcon/turnt-ninja/HEAD/src/TurntNinja/Content/Fonts/Open Sans/OpenSans-SemiboldItalic.ttf -------------------------------------------------------------------------------- /src/TurntNinja/Content/Songs/song attribution.txt: -------------------------------------------------------------------------------- 1 | Never-Stop - Whatfunk - http://www.whatfunk.com/ - CC0 1.0 Universal (CC0 1.0) 2 | Wayfarer - Whatfunk - http://www.whatfunk.com/ - CC0 1.0 Universal (CC0 1.0) -------------------------------------------------------------------------------- /src/TurntNinja/Content/Shaders/simple.vs: -------------------------------------------------------------------------------- 1 | 2 | uniform mat4 mvp; 3 | uniform vec4 in_color; 4 | 5 | in vec2 in_position; 6 | out vec4 vs_color; 7 | 8 | void main(void) 9 | { 10 | gl_Position = mvp * vec4(in_position.xy, 0., 1.0); 11 | vs_color = in_color; 12 | } 13 | -------------------------------------------------------------------------------- /src/TurntNinja/Content/Shaders/colour.vs: -------------------------------------------------------------------------------- 1 | #version 130 2 | 3 | uniform mat4 mvp; 4 | 5 | in vec2 in_position; 6 | in vec4 in_color; 7 | out vec4 vs_color; 8 | 9 | void main(void) 10 | { 11 | gl_Position = mvp * vec4(in_position.xy, 0., 1.0); 12 | vs_color = in_color; 13 | } -------------------------------------------------------------------------------- /src/TurntNinja/CSCore.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/TurntNinja/Audio/SongCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TurntNinja.Audio 8 | { 9 | class SongCache 10 | { 11 | int _maximumSongCount; 12 | List _songs; 13 | 14 | public SongCache() 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TurntNinja/paket.references: -------------------------------------------------------------------------------- 1 | ColorMine 2 | CSCore 3 | CSCore.OSX 4 | DeltaCompressionDotNet 5 | Gwen 6 | Gwen.Renderer.OpenTK 7 | HUSL 8 | LiteDB 9 | MathNet.Numerics 10 | Mono.Cecil 11 | Newtonsoft.Json 12 | OnsetDetection 13 | OpenTK content: none 14 | QuickFont.Desktop 15 | SharpFont import_targets: false 16 | SharpFont.Dependencies 17 | Splat 18 | squirrel.windows 19 | TagLib.Portable 20 | SharpRaven 21 | AcoustID.NET -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.11.{build}-appveyor 2 | image: Visual Studio 2015 3 | environment: 4 | BUTLER_API_KEY: 5 | secure: 1YditInrUO36VFxX37IzVWUC16FEwJ238DsqhKpgMxuRzyTCR8MTFMbEs6LiNY1f 6 | configuration: 7 | - Debug 8 | - Release 9 | build: 10 | parallel: true 11 | verbosity: minimal 12 | build_script: 13 | - cmd: .\build.cmd mode=%CONFIGURATION% 14 | after_build: 15 | - cmd: .\build.cmd PushArtifactsAndItchBuilds mode=%CONFIGURATION% 16 | -------------------------------------------------------------------------------- /turnt-ninja.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /src/TurntNinja/Audio/IAudioAnalyser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace BeatDetection.Audio 8 | { 9 | interface IAudioAnalyser 10 | { 11 | List ExtractOnsets(string audioFilePath); 12 | bool SongAnalysed(string audioFilePath); 13 | } 14 | 15 | struct AnalysisArguments 16 | { 17 | public string CSVDirectory; 18 | public string AudioFilePath; 19 | public string InitialOutputSuffix; 20 | public string DesiredOutputSuffix; 21 | public float Correction; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/TurntNinja/FileSystem/FileBrowserEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TurntNinja.FileSystem 8 | { 9 | public struct FileBrowserEntry 10 | { 11 | public string Path; 12 | public string Name; 13 | public FileBrowserEntryType EntryType; 14 | } 15 | 16 | [Flags] 17 | public enum FileBrowserEntryType 18 | { 19 | Song = 1 << 0, 20 | Directory = 1 << 1, 21 | Drive = 1 << 2, 22 | Special = 1 << 3, 23 | Separator = 1 << 4, 24 | Plugin = 1 << 5 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Turnt Ninja [![Build Status](https://travis-ci.org/opcon/turnt-ninja.svg)](https://travis-ci.org/opcon/turnt-ninja) [![Build status](https://ci.appveyor.com/api/projects/status/5s980fxi2feotl37?svg=true)](https://ci.appveyor.com/project/opcon/turnt-ninja) 2 | =========== 3 | 4 | A beat-fighting-ninja-like-get-turnt rhythm game inspired by the wonderful Super Hexagon by Terry Cavanagh. 5 | 6 | # Screenshots 7 | 8 | ![](http://i.imgur.com/ZAPvaLX.png) 9 | ![](http://i.imgur.com/jKDVc0T.png) 10 | 11 | # Gameplay Videos 12 | [![](http://i.imgur.com/oEg1f7d.jpg)](https://streamable.com/5b3b) 13 | 14 | # License 15 | 16 | GPL, see the LICENSE file in the repository root 17 | -------------------------------------------------------------------------------- /src/TurntNinja/TurntNinja.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | turnt_ninja 5 | @build.number@ 6 | @project@ 7 | @authors@ 8 | $author$ 9 | https://github.com/opcon/turnt-ninja/blob/master/LICENSE 10 | https://github.com/opcon/turnt-ninja 11 | false 12 | https://github.com/opcon/turnt-ninja 13 | @releaseNotes@ 14 | Copyright 2016 15 | 16 | @files@ 17 | -------------------------------------------------------------------------------- /src/TurntNinja/Audio/PlaylistHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TurntNinja.Audio 9 | { 10 | public static class PlaylistHelper 11 | { 12 | public static List LoadPlaylist(string playlistFile) 13 | { 14 | StreamReader file = File.OpenText(playlistFile); 15 | var lines = new List(); 16 | string line; 17 | while ((line = file.ReadLine()) != null) 18 | { 19 | //check that this line is not a comment 20 | if (line.StartsWith("#")) continue; 21 | lines.Add(line); 22 | } 23 | 24 | return lines; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/TurntNinja/FileSystem/IFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using CSCore; 8 | using TurntNinja.Audio; 9 | 10 | 11 | namespace TurntNinja.FileSystem 12 | { 13 | public interface IFileSystem 14 | { 15 | List FileSystemCollection { get; set; } 16 | ReadOnlyCollection FileSystemEntryCollection { get; } 17 | string FriendlyName { get; } 18 | 19 | int Initialise(FileBrowserEntry separator); 20 | void Focused(); 21 | 22 | bool EntrySelected(ref int entryIndex); 23 | bool SongExists(SongBase song); 24 | 25 | Song LoadSongInformation(int entryIndex); 26 | void LoadSongAudio(Song song); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/TurntNinja/Game/HighScore.cs: -------------------------------------------------------------------------------- 1 | using LiteDB; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TurntNinja.Game 9 | { 10 | public class HighScoreEntry 11 | { 12 | public ObjectId Id { get; set; } 13 | public string SongName { get; set; } 14 | public long SongID { get; set; } 15 | public List HighScores { get; set; } 16 | public DifficultyLevels Difficulty { get; set; } 17 | 18 | public HighScoreEntry() 19 | { 20 | HighScores = new List(); 21 | } 22 | } 23 | 24 | public class PlayerScore 25 | { 26 | public string Name { get; set; } 27 | public long Score { get; set; } 28 | public float Accuracy { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /scripts/package-files.ps1: -------------------------------------------------------------------------------- 1 | $major = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FileMajorPart 2 | $minor = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FileMinorPart 3 | $build = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FileBuildPart 4 | $name = 'turnt-ninja_v{0}_{1}_{2}' -f $major, $minor, $build 5 | echo $name 6 | 7 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $name .\Binaries\Release/*.dll 8 | Start-Sleep -Milliseconds 500 9 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $name .\Binaries\Release\turnt-ninja.exe 10 | Start-Sleep -Milliseconds 500 11 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $name .\Binaries\Release\turnt-ninja.exe.config 12 | Start-Sleep -Milliseconds 500 13 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $name .\Resources\ 14 | Start-Sleep -Milliseconds 500 15 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $name .\Licenses\ 16 | Start-Sleep -Milliseconds 500 17 | & 'C:\Program Files\WinRAR\Rar.exe' a $name '.\Turnt Ninja.lnk' 18 | Start-Sleep -Milliseconds 500 -------------------------------------------------------------------------------- /scripts/package-files-new.ps1: -------------------------------------------------------------------------------- 1 | $ENV:major = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FileMajorPart 2 | $ENV:minor = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FileMinorPart 3 | $ENV:build = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FileBuildPart 4 | $ENV:private = (Get-Item .\Binaries\Release\turnt-ninja.exe).VersionInfo.FilePrivatePart 5 | $ENV:name = 'turnt-ninja_v{0}_{1}_{2}' -f $ENV:major, $ENV:minor, $ENV:build 6 | echo $ENV:name 7 | 8 | & 'C:\Program Files\WinRAR\Rar.exe' a -ep -r $ENV:name .\Binaries\Release/*.dll 9 | Start-Sleep -Milliseconds 500 10 | & 'C:\Program Files\WinRAR\Rar.exe' a -ep -r $ENV:name .\Binaries\Release\turnt-ninja.exe 11 | Start-Sleep -Milliseconds 500 12 | & 'C:\Program Files\WinRAR\Rar.exe' a -ep -r $ENV:name .\Binaries\Release\turnt-ninja.exe.config 13 | Start-Sleep -Milliseconds 500 14 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $ENV:name .\Resources\ 15 | Start-Sleep -Milliseconds 500 16 | & 'C:\Program Files\WinRAR\Rar.exe' a -r $ENV:name .\Licenses\ 17 | Start-Sleep -Milliseconds 500 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | language: csharp 5 | env: 6 | matrix: 7 | - CONFIGURATION=Release 8 | - CONFIGURATION=Debug 9 | global: 10 | secure: Hgz9igzcHhxN8gSxaaVqRN3YuNEeGPDoeMhv8rfZhXrfVaVXebZhiPO1A7r9ZElzNu2XlbheAPAUhSbmG5wx6N8DIypUqqRUwPyPzAB3N/zmBzh6rJtLnauzG/hmOemNApQ8VY9A89vS9+c22h+e8ULlPzd2U2D14mNC31TcoLM1foaougmLdtedWXGoS4W8WGi6tiuBIiyJxomlS6kUMhvRuLddzHGb9ctbu1NbyAk1TmGIWbVPLoOJ46ali9wdqBDsU8QGLasHZvcIMB51dld2/NxOlzWrgu6Q1GWTNW51uld/yErmuZzRg9jJebPUfWqOIOVcv9UmTH9wFPj4J2S+18uiO1o+UEPMRfDVY+apSgfepTRJ5WCZrSKseDzqUL9ObjM7bGEpTChk/fNQM2RXAO7yXevGp9GvRK/vLxQuIAhwyOG7uvT3xqMKR2ZoiXZrLhmpWu/4T3K5P09dn/m57DgZe37DRnWMEckqNbPfGsIg4l0EMx3jSFKNF/ZDMpy2DMn9Iy8FgWAGB1OysVxXy+yuDthgqUkUsGIdiQKIa/EswTWB9XwzjABTlo1NoDqDJZUXMdkvVuMCrIMZshfEA4DkI7ZaFoVMEeXADcjZ9c5oi4Xt/0XfOjxAeTfFSXcw+BInytCel37SM7/3hKbe7utuM3nNqgUA3vszNa0= 11 | script: 12 | - "./build.sh mode=$CONFIGURATION" 13 | - "./build.sh PushArtifactsAndItchBuilds mode=$CONFIGURATION" 14 | notifications: 15 | email: 16 | on_success: change 17 | on_failure: always 18 | on_start: never 19 | -------------------------------------------------------------------------------- /docs/licenses/QuickFont.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/licenses/ColorMine.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 ColorMine.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /docs/licenses/Substructio.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Patrick Yates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/TurntNinja/Audio/SongBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TurntNinja.Audio 8 | { 9 | public class SongBase : IEquatable 10 | { 11 | public string FileSystemFriendlyName { get; set; } 12 | public string InternalName { get; set; } 13 | 14 | public string Identifier { get; set; } 15 | public string Artist { get; set; } 16 | public string TrackName { get; set; } 17 | 18 | public bool Equals(SongBase other) 19 | { 20 | return (FileSystemFriendlyName.Equals(other.FileSystemFriendlyName)) 21 | && (InternalName.Equals(other.InternalName)) 22 | && (Identifier.Equals(other.Identifier)) 23 | && (Artist.Equals(other.Artist)) 24 | && (TrackName.Equals(other.TrackName)); 25 | } 26 | 27 | public override bool Equals(object obj) 28 | { 29 | SongBase s = obj as SongBase; 30 | if (s != null) 31 | return Equals(s); 32 | return false; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/TurntNinja/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace TurntNinja.Properties { 2 | 3 | 4 | // This class allows you to handle specific events on the settings class: 5 | // The SettingChanging event is raised before a setting's value is changed. 6 | // The PropertyChanged event is raised after a setting's value is changed. 7 | // The SettingsLoaded event is raised after the setting values are loaded. 8 | // The SettingsSaving event is raised before the setting values are saved. 9 | internal sealed partial class Settings { 10 | 11 | public Settings() { 12 | // // To add event handlers for saving and changing settings, uncomment the lines below: 13 | // 14 | // this.SettingChanging += this.SettingChangingEventHandler; 15 | // 16 | // this.SettingsSaving += this.SettingsSavingEventHandler; 17 | // 18 | } 19 | 20 | private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) { 21 | // Add code to handle the SettingChangingEvent event here. 22 | } 23 | 24 | private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) { 25 | // Add code to handle the SettingsSaving event here. 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/TurntNinja/Audio/Song.cs: -------------------------------------------------------------------------------- 1 | using TurntNinja.FileSystem; 2 | using CSCore; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TurntNinja.Audio 10 | { 11 | public class Song : IDisposable 12 | { 13 | public IWaveSource SongAudio { get; set; } 14 | public bool SongAudioLoaded { get; set; } = false; 15 | public IFileSystem FileSystem { get; set; } 16 | public SongBase SongBase { get; set; } 17 | 18 | bool disposedValue = false; // To detect redundant calls 19 | 20 | public void LoadSongAudio() 21 | { 22 | FileSystem.LoadSongAudio(this); 23 | } 24 | 25 | protected virtual void Dispose(bool disposing) 26 | { 27 | if (!disposedValue) 28 | { 29 | if (disposing) 30 | { 31 | SongAudio.Dispose(); 32 | } 33 | SongAudio = null; 34 | 35 | disposedValue = true; 36 | } 37 | } 38 | 39 | // This code added to correctly implement the disposable pattern. 40 | public void Dispose() 41 | { 42 | // Do not change this code. Put cleanup code in Dispose(bool disposing) above. 43 | Dispose(true); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/TurntNinja/OpenTK.dll.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/souncloud-api-v2-notes.md: -------------------------------------------------------------------------------- 1 | # Explore API 2 | 3 | **Get explore categories** 4 | 5 | ``` 6 | https://api-v2.soundcloud.com/explore/categories?limit=10&offset=0&linked_partitioning=1&client_id=ID_HERE 7 | ``` 8 | 9 | Example 10 | ``` 11 | https://api-v2.soundcloud.com/explore/categories?limit=10&offset=0&linked_partitioning=1&client_id=02gUJC0hH2ct1EGOcYXQIzRFU91c72Ea&app_version=d8c55ad 12 | ``` 13 | 14 | **Get tracks from explore category** 15 | ``` 16 | https://api-v2.soundcloud.com/explore/CATEGORY_NAME_HERE?tag=out-of-experiment&limit=10&offset=0&linked_partitioning=1&client_id=ID_HERE 17 | ``` 18 | 19 | Example 20 | ``` 21 | https://api-v2.soundcloud.com/explore/Hip+Hop+%26+Rap?tag=out-of-experiment&limit=10&client_id=ID_HERE 22 | ``` 23 | 24 | # New Search API 25 | 26 | **Search Everything** 27 | ``` 28 | https://api-v2.soundcloud.com/search?q=antidote&facet=model&user_id=698189-13257-213778-325874&limit=10&offset=0&linked_partitioning=1&client_id=02gUJC0hH2ct1EGOcYXQIzRFU91c72Ea&app_version=d8c55ad 29 | ``` 30 | 31 | **Search only tracks** 32 | ``` 33 | https://api-v2.soundcloud.com/search/tracks?q=antidote&facet=genre&user_id=698189-13257-213778-325874&limit=10&offset=0&linked_partitioning=1&client_id=02gUJC0hH2ct1EGOcYXQIzRFU91c72Ea&app_version=d8c55ad 34 | ``` 35 | 36 | # New Charts API 37 | 38 | **Get All-Music Chart** 39 | ``` 40 | https://api-v2.soundcloud.com/charts?kind=top&genre=soundcloud%3Agenres%3Aall-music&client_id=02gUJC0hH2ct1EGOcYQXIzRFU91c72Ea&limit=20&offset=0&linked_partitioning=1&app_version=1461312517 41 | ``` -------------------------------------------------------------------------------- /scripts/create-squirrel-package.ps1: -------------------------------------------------------------------------------- 1 | # create release file 2 | .\package-files-new.ps1 3 | 4 | # load release notes 5 | $rel = Get-Content .\release-notes.txt -Raw 6 | 7 | # do directory stuff 8 | $basedir = '.\SquirrelReleases\{0}_{1}_{2}_{3}' -f $ENV:major, $ENV:minor, $ENV:build, $ENV:private 9 | $dirname = '{0}\lib\net45' -f $basedir 10 | md -Force $dirname 11 | & 'C:\Program Files\WinRAR\UnRAR.exe' x -o+ $env:name $dirname 12 | pushd $basedir 13 | ..\..\.nuget\NuGet.exe spec -a '.\lib\net45\turnt-ninja.exe' -f 14 | # update spec info 15 | $xml = [xml](Get-Content 'turnt-ninja.nuspec') 16 | 17 | $xml.package.metadata.licenseUrl = "https://github.com/opcon/turnt-ninja/blob/master/LICENSE" 18 | $xml.package.metadata.projectUrl = "https://github.com/opcon/turnt-ninja" 19 | $xml.package.metadata.releaseNotes = "$rel" 20 | 21 | # delete some entries 22 | $remove = $xml.package.metadata.dependencies 23 | $xml.package.metadata.RemoveChild($remove) 24 | 25 | $remove = $xml.SelectSingleNode("//tags") 26 | $xml.package.metadata.RemoveChild($remove) 27 | 28 | $remove = $xml.SelectSingleNode("//iconUrl") 29 | $xml.package.metadata.RemoveChild($remove) 30 | 31 | # save spec file 32 | $xml.Save(("$pwd\turnt-ninja.nuspec")) 33 | ..\..\.nuget\NuGet.exe pack 'turnt-ninja.nuspec' 34 | popd 35 | 36 | # squirrel releasify 37 | $packageFile = "$basedir\turnt-ninja.$ENV:major.$ENV:minor.$ENV:build.$ENV:private.nupkg" 38 | $arguments = "--releasify $packageFile" 39 | Start-Process '.\packages\squirrel.windows.1.4.0\tools\Squirrel.exe' -ArgumentList $arguments -Wait -------------------------------------------------------------------------------- /src/TurntNinja/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Turnt Ninja")] 9 | [assembly: AssemblyDescription("Turnt Ninja")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("VirtualHighRise")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("Copyright Patrick Yates © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d4cccf09-83d4-4190-bb8f-4fca1b7451b8")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.14.*")] 36 | [assembly: AssemblyInformationalVersion("")] 37 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | redirects: on 2 | 3 | source https://ci.appveyor.com/nuget/gwen-nolegacy-opentk-renderer-y8bf4l8s9nxo 4 | source https://ci.appveyor.com/nuget/onset-detection-ce96cu4ph0k5 5 | source https://ci.appveyor.com/nuget/quickfont-m6wdxb2yf1v5 6 | source https://ci.appveyor.com/nuget/cscore-lt381sf5ht3e 7 | source https://www.nuget.org/api/v2/ 8 | 9 | nuget AcoustID.NET 10 | nuget CSCore.OSX = 1.1.34 11 | nuget NuGet.CommandLine 12 | nuget ColorMine >= 1.1.3.0 framework: >= net45 13 | nuget CSCore = 1.1.34 framework: >= net45 14 | nuget Gwen >= 4.0 framework: >= net45 15 | nuget Gwen.Renderer.OpenTK >= 4.0 framework: >= net45 16 | nuget DeltaCompressionDotNet 1.0.0 framework: >= net45 17 | nuget HUSL >= 1.0.1 framework: >= net45 18 | nuget LiteDB >= 1.0.4 framework: >= net45 19 | nuget MathNet.Numerics >= 3.11.1 framework: >= net45 20 | nuget Mono.Cecil 0.9.6.1 framework: >= net45 21 | nuget Newtonsoft.Json >= 8.0.3 framework: >= net45 22 | nuget OnsetDetection >= 1.0.6020.16658 framework: >= net45 23 | nuget OpenTK >= 2.0 framework: >= net45 24 | nuget QuickFont.Desktop >= 4.3.6020.17547 framework: >= net45 25 | nuget SharpFont >= 3.1.0 framework: >= net45 26 | nuget SharpFont.Dependencies >= 2.6 framework: >= net45 27 | nuget SharpRaven 28 | nuget Splat >= 1.6.2 framework: >= net45 29 | nuget squirrel.windows >= 1.4.0 framework: >= net45 30 | nuget TagLib.Portable >= 1.0.4 framework: >= net45 31 | 32 | group Deploy 33 | 34 | source https://www.nuget.org/api/v2/ 35 | nuget ILRepack 36 | nuget Microsoft.Data.Services.Client = 5.6.1 37 | 38 | group Build 39 | 40 | source https://www.nuget.org/api/v2/ 41 | nuget FAKE 42 | -------------------------------------------------------------------------------- /docs/licenses/Clipper.txt: -------------------------------------------------------------------------------- 1 | The Clipper Library (including Delphi, C++ & C# source code, other accompanying 2 | code, examples and documentation), hereafter called "the Software", has been 3 | released under the following license, terms and conditions: 4 | 5 | Boost Software License - Version 1.0 - August 17th, 2003 6 | http://www.boost.org/LICENSE_1_0.txt 7 | 8 | Permission is hereby granted, free of charge, to any person or organization 9 | obtaining a copy of the Software covered by this license to use, reproduce, 10 | display, distribute, execute, and transmit the Software, and to prepare 11 | derivative works of the Software, and to permit third-parties to whom the 12 | Software is furnished to do so, all subject to the following: 13 | 14 | The copyright notices in the Software and this entire statement, including the 15 | above license grant, this restriction and the following disclaimer, must be 16 | included in all copies of the Software, in whole or in part, and all derivative 17 | works of the Software, unless such copies or derivative works are solely in the 18 | form of machine-executable object code generated by a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL 23 | THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY 24 | DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /lib/Clipper/License.txt: -------------------------------------------------------------------------------- 1 | The Clipper Library (including Delphi, C++ & C# source code, other accompanying 2 | code, examples and documentation), hereafter called "the Software", has been 3 | released under the following license, terms and conditions: 4 | 5 | Boost Software License - Version 1.0 - August 17th, 2003 6 | http://www.boost.org/LICENSE_1_0.txt 7 | 8 | Permission is hereby granted, free of charge, to any person or organization 9 | obtaining a copy of the Software covered by this license to use, reproduce, 10 | display, distribute, execute, and transmit the Software, and to prepare 11 | derivative works of the Software, and to permit third-parties to whom the 12 | Software is furnished to do so, all subject to the following: 13 | 14 | The copyright notices in the Software and this entire statement, including the 15 | above license grant, this restriction and the following disclaimer, must be 16 | included in all copies of the Software, in whole or in part, and all derivative 17 | works of the Software, unless such copies or derivative works are solely in the 18 | form of machine-executable object code generated by a source language processor. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL 23 | THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY 24 | DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /src/TurntNinja/Core/HUSLColor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections; 4 | using OpenTK.Graphics; 5 | 6 | namespace TurntNinja.Core 7 | { 8 | public struct HUSLColor 9 | { 10 | public double H; 11 | public double S; 12 | public double L; 13 | 14 | public HUSLColor (double h, double s, double l) 15 | { 16 | H = h; 17 | S = s; 18 | L = l; 19 | } 20 | 21 | public HUSLColor(Color4 color) 22 | { 23 | //var c = new ColorMine.ColorSpaces.Rgb { R = color.R*255, G = color.G*255, B = color.B*255 }; 24 | //var r = c.To(); 25 | //H = r.H; 26 | //S = r.S; 27 | //L = r.L; 28 | var res = HUSL.ColorConverter.RGBToHUSL(new List { color.R, color.G, color.B }); 29 | H = res[0]; 30 | S = res[1]; 31 | L = res[2]; 32 | } 33 | 34 | public static HUSLColor FromColor4(Color4 color) 35 | { 36 | return new HUSLColor(color); 37 | } 38 | 39 | public static Color4 ToColor4(HUSLColor color) 40 | { 41 | //var c = new ColorMine.ColorSpaces.Hsl { H = color.H, L = color.L, S = color.S }; 42 | //var r = c.ToRgb(); 43 | //return new Color4((float)r.R/255, (float)r.G/255, (float)r.B/255, 1.0f); 44 | var res = HUSL.ColorConverter.HUSLToRGB(new List{ color.H, color.S, color.L }); 45 | return new Color4((byte)((res[0]) * 255), (byte)((res[1]) * 255), (byte)((res[2]) * 255), 255); 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /turnt-ninja.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{9281E6FF-BFC1-45FA-9EE5-2E50D35E0D58}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TurntNinja", "src\TurntNinja\TurntNinja.csproj", "{630022B3-A336-42AA-85D5-5AACE71FDE6C}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Substructio", "..\Substructio\src\Substructio.csproj", "{5B3F1066-88DA-4F9C-8DFE-646C40B412C2}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BB0E8FBB-6D2E-4F49-A59E-F90B598AA4E6}" 16 | ProjectSection(SolutionItems) = preProject 17 | build.fsx = build.fsx 18 | EndProjectSection 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {630022B3-A336-42AA-85D5-5AACE71FDE6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {630022B3-A336-42AA-85D5-5AACE71FDE6C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {630022B3-A336-42AA-85D5-5AACE71FDE6C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {630022B3-A336-42AA-85D5-5AACE71FDE6C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {5B3F1066-88DA-4F9C-8DFE-646C40B412C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {5B3F1066-88DA-4F9C-8DFE-646C40B412C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {5B3F1066-88DA-4F9C-8DFE-646C40B412C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {5B3F1066-88DA-4F9C-8DFE-646C40B412C2}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /src/TurntNinja/Audio/AcoustIDCSCore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using AcoustID.Chromaprint; 7 | using CSCore; 8 | 9 | namespace TurntNinja.Audio 10 | { 11 | class AcoustIDCSCore : AcoustID.Audio.IDecoder, IDisposable 12 | { 13 | private readonly CSCore.IWaveSource _waveSource; 14 | private const int BUFFER_SIZE = 2 * 192000; 15 | 16 | private byte[] _buffer; 17 | private short[] _data; 18 | 19 | public AcoustIDCSCore(CSCore.IWaveSource waveSource) 20 | { 21 | _waveSource = waveSource; 22 | 23 | if (_waveSource.WaveFormat.BitsPerSample != 16) 24 | throw new ArgumentOutOfRangeException(nameof(waveSource), "Expected 16 bit audio"); 25 | } 26 | 27 | public int Channels { get { return _waveSource.WaveFormat.Channels; } } 28 | 29 | public int SampleRate { get { return _waveSource.WaveFormat.SampleRate; } } 30 | 31 | public bool Decode(IAudioConsumer consumer, int maxLength) 32 | { 33 | if (_waveSource == null) return false; 34 | if (_buffer == null) _buffer = new byte[BUFFER_SIZE * 2]; 35 | if (_data == null) _data = new short[BUFFER_SIZE]; 36 | 37 | // maxLength is in seconds of audio 38 | // Calculate maximum bytes to read 39 | var maxBytes = maxLength * Channels * SampleRate; 40 | 41 | // Calculate actual bytes we can fit in buffer 42 | var bytesToRead = Math.Min(maxBytes, _buffer.Length); 43 | 44 | int read = 0; 45 | 46 | while ((read = _waveSource.Read(_buffer, 0, bytesToRead)) > 0) 47 | { 48 | Buffer.BlockCopy(_buffer, 0, _data, 0, read); 49 | 50 | consumer.Consume(_data, read / 2); 51 | 52 | maxBytes -= read / 2; 53 | if (maxBytes <= 0) 54 | break; 55 | bytesToRead = Math.Min(maxBytes, _buffer.Length); 56 | } 57 | 58 | return true; 59 | } 60 | 61 | public void Dispose() 62 | { 63 | _waveSource.Dispose(); 64 | _buffer = null; 65 | _data = null; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/licenses/OpenTK.txt: -------------------------------------------------------------------------------- 1 | The Open Toolkit library license 2 | Copyright (c) 2006 - 2014 Stefanos Apostolopoulos (stapostol@gmail.com) for the Open Toolkit library. 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | Third parties 7 | OpenTK.Platform.Windows and OpenTK.Platform.X11 include portions of the Mono class library. These portions are covered by the following license: 8 | Copyright (c) 2004 Novell, Inc. 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | REDIRECTS: ON 2 | NUGET 3 | remote: https://www.nuget.org/api/v2 4 | AcoustID.NET (1.3.2.1) 5 | ColorMine (1.1.3) - framework: >= net45 6 | DeltaCompressionDotNet (1.0) - framework: >= net45 7 | HUSL (1.0.1) - framework: >= net45 8 | LiteDB (3.0.1) - framework: >= net45 9 | MathNet.Numerics (3.17) - framework: >= net45 10 | Mono.Cecil (0.9.6.1) - framework: >= net45 11 | Newtonsoft.Json (9.0.1) 12 | NuGet.CommandLine (3.5) 13 | OpenTK (2.0) - framework: >= net45 14 | SharpFont (4.0.1) - framework: >= net45 15 | SharpFont.Dependencies 16 | SharpRaven (2.2) 17 | Newtonsoft.Json (>= 6.0.8) 18 | Splat (2.0) - framework: >= net45 19 | squirrel.windows (1.5.2) - framework: >= net45 20 | DeltaCompressionDotNet (>= 1.0 < 2.0) 21 | Mono.Cecil (>= 0.9.6.1) 22 | Splat (>= 1.6.2) 23 | TagLib.Portable (1.0.4) - framework: >= net45 24 | remote: https://ci.appveyor.com/nuget/cscore-lt381sf5ht3e 25 | CSCore (1.1.34) 26 | CSCore.OSX (1.1.34) 27 | CSCore (>= 1.1.34) 28 | remote: https://ci.appveyor.com/nuget/gwen-nolegacy-opentk-renderer-y8bf4l8s9nxo 29 | Gwen (4.0.6207.13461) - framework: >= net45 30 | OpenTK (>= 2.0) 31 | Gwen.Renderer.OpenTK (4.0.6207.13461) - framework: >= net45 32 | Gwen (>= 4.0) 33 | OpenTK (>= 2.0) 34 | QuickFont.Desktop (>= 4.3.6018.34176) 35 | SharpFont (>= 3.1) 36 | SharpFont.Dependencies (>= 2.6) 37 | remote: https://ci.appveyor.com/nuget/onset-detection-ce96cu4ph0k5 38 | OnsetDetection (1.0.6071.26192) - framework: >= net45 39 | CSCore (>= 1.1.15) 40 | MathNet.Numerics (>= 3.11.1) 41 | remote: https://ci.appveyor.com/nuget/quickfont-m6wdxb2yf1v5 42 | QuickFont.Desktop (4.4.6239.14169) - framework: >= net45 43 | OpenTK (>= 2.0) 44 | SharpFont (>= 4.0) 45 | SharpFont.Dependencies (>= 2.6) 46 | SharpFont.Dependencies (2.6) - framework: >= net45 47 | 48 | GROUP Build 49 | NUGET 50 | remote: https://www.nuget.org/api/v2 51 | FAKE (4.50.1) 52 | 53 | GROUP Deploy 54 | NUGET 55 | remote: https://www.nuget.org/api/v2 56 | ILRepack (2.0.12) 57 | Microsoft.Data.Edm (5.6.1) 58 | Microsoft.Data.OData (5.6.1) 59 | Microsoft.Data.Edm (5.6.1) 60 | System.Spatial (5.6.1) 61 | Microsoft.Data.Services.Client (5.6.1) 62 | Microsoft.Data.OData (5.6.1) 63 | System.Spatial (5.6.1) 64 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /docs/licenses/CSCore.md: -------------------------------------------------------------------------------- 1 | ## Microsoft Public License (Ms-PL) ## 2 | 3 | Microsoft Public License (Ms-PL) 4 | 5 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 6 | 7 | #### 1. Definitions #### 8 | 9 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 10 | 11 | A "contribution" is the original software, or any additions or changes to the software. 12 | 13 | A "contributor" is any person that distributes its contribution under this license. 14 | 15 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 16 | 17 | #### 2. Grant of Rights #### 18 | 19 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 20 | 21 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 22 | 23 | #### 3. Conditions and Limitations #### 24 | 25 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 26 | 27 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 28 | 29 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 30 | 31 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 32 | 33 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 34 | -------------------------------------------------------------------------------- /scripts/Butler.fs: -------------------------------------------------------------------------------- 1 | module Fake.Butler 2 | 3 | open System.Net 4 | 5 | let butler_linux_32 = "https://dl.itch.ovh/butler/linux-386/head/butler" 6 | let butler_linux_64 = "https://dl.itch.ovh/butler/linux-amd64/head/butler" 7 | let butler_mac = "https://dl.itch.ovh/butler/darwin-amd64/head/butler" 8 | let butler_windows_32 = "https://dl.itch.ovh/butler/windows-386/head/butler.exe" 9 | let butler_windows_64 = "https://dl.itch.ovh/butler/windows-amd64/head/butler.exe" 10 | 11 | let butlerFileName = 12 | match EnvironmentHelper.isWindows with 13 | | true -> "butler.exe" 14 | | false -> "butler" 15 | 16 | let butlerURL = 17 | if EnvironmentHelper.isLinux then match System.Environment.Is64BitOperatingSystem with 18 | | true -> butler_linux_64 19 | | false -> butler_linux_32 20 | else if EnvironmentHelper.isMacOS then butler_mac 21 | else if EnvironmentHelper.isWindows then match System.Environment.Is64BitOperatingSystem with 22 | | true -> butler_windows_64 23 | | false -> butler_windows_32 24 | else butler_windows_32 25 | 26 | let DownloadButler (butlerFolder:string) = 27 | let wc = new WebClient() 28 | try 29 | wc.DownloadFile(butlerURL, butlerFolder + butlerFileName) 30 | with 31 | | _ -> (let SSLUri = new System.Uri(butlerURL) 32 | let uriBuilder = new System.UriBuilder(butlerURL) 33 | uriBuilder.Scheme <- System.Uri.UriSchemeHttp 34 | uriBuilder.Port <- -1 35 | let nonSSLURL = uriBuilder.ToString() 36 | trace ("Download failed, trying non-ssl url " + nonSSLURL) 37 | wc.DownloadFile(nonSSLURL, butlerFolder + butlerFileName)) 38 | match EnvironmentHelper.isWindows with 39 | | false -> (let result = ProcessHelper.ExecProcess (fun info -> 40 | info.FileName <- "chmod" 41 | info.Arguments <- "u+x " + butlerFolder + butlerFileName) (System.TimeSpan.FromSeconds 10.0) 42 | if result <> 0 then failwithf "Couldn't set executable permissions on bulter") 43 | | _ -> () 44 | trace ("Butler downloaded to " + (butlerFolder + butlerFileName)) 45 | 46 | let PushBuild (butlerFolder:string) (buildFileOrDir:string) (target:string) (channel:string) (version:string) (fixPermissions:bool) = 47 | let butlerFullPath = butlerFolder + butlerFileName 48 | 49 | let flags = 50 | "" + (if fixPermissions then "--fix-permissions " else "") 51 | + ("--userversion=" + version + " ") 52 | 53 | ProcessHelper.redirectOutputToTrace = true |> ignore 54 | let result = ProcessHelper.ExecProcess (fun info -> 55 | info.FileName <- butlerFullPath 56 | info.Arguments <- (sprintf "push %s%s %s:%s" flags buildFileOrDir target channel)) (System.TimeSpan.FromMinutes 5.0) 57 | if result <> 0 then failwithf "Butler push failed" 58 | result -------------------------------------------------------------------------------- /src/TurntNinja/Core/Settings/PropertySettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.Linq; 6 | using System.Threading; 7 | using Gwen.Control; 8 | using Substructio.Core.Settings; 9 | 10 | namespace TurntNinja.Core.Settings 11 | { 12 | class PropertySettings : IGameSettings 13 | { 14 | private bool _loaded; 15 | private Dictionary _settings; 16 | 17 | public object this[string key] 18 | { 19 | get 20 | { 21 | if (!_loaded) throw new Exception("GameSettings was not loaded before access"); 22 | if (_settings.ContainsKey(key)) return _settings[key].PropertyValue; 23 | throw new GameSettingNotFoundException("The game setting with key {0} was not found", key); 24 | } 25 | set { if (_settings.ContainsKey(key)) _settings[key].PropertyValue = value; } 26 | } 27 | 28 | public void Save() 29 | { 30 | Properties.Settings.Default.Save(); 31 | } 32 | 33 | public void Load() 34 | { 35 | if (_loaded) throw new Exception("Game settings already loaded, can't load again"); 36 | if (Properties.Settings.Default.UpgradeRequired) 37 | { 38 | Properties.Settings.Default.Upgrade(); 39 | Properties.Settings.Default.UpgradeRequired = false; 40 | Properties.Settings.Default.Save(); 41 | } 42 | 43 | //Properties.Settings.Default.Reload(); 44 | //load all the settings 45 | _settings = new Dictionary(); 46 | SettingsPropertyValue[] values = new SettingsPropertyValue[Properties.Settings.Default.PropertyValues.Count]; 47 | Properties.Settings.Default.PropertyValues.CopyTo(values, 0); 48 | foreach (SettingsPropertyValue propertyValue in values) 49 | { 50 | _settings.Add(propertyValue.Name, propertyValue); 51 | } 52 | _loaded = true; 53 | } 54 | 55 | public Dictionary GetAllSettings() 56 | { 57 | var ret = new Dictionary(); 58 | foreach (var kvp in _settings) 59 | { 60 | ret.Add(kvp.Key, kvp.Value.PropertyValue); 61 | } 62 | return ret; 63 | } 64 | } 65 | 66 | public class GameSettingNotFoundException : Exception 67 | { 68 | public GameSettingNotFoundException() : base() 69 | { 70 | } 71 | 72 | public GameSettingNotFoundException(string message) : base(message) 73 | { 74 | } 75 | 76 | public GameSettingNotFoundException(string message, Exception innerException) : base(message, innerException) 77 | { 78 | } 79 | 80 | public GameSettingNotFoundException(string format, params object[] args) : base(string.Format(format, args)) 81 | { 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/TurntNinja/FileSystem/RecentFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TurntNinja.Audio; 8 | using System.IO; 9 | 10 | namespace TurntNinja.FileSystem 11 | { 12 | class RecentFileSystem : IFileSystem 13 | { 14 | List _recentSongs; 15 | List _recentSongList; 16 | List _recentSongBaseList; 17 | 18 | public ReadOnlyCollection FileSystemEntryCollection { get { return _recentSongs.AsReadOnly(); } } 19 | 20 | public string FriendlyName { get { return "Recent Songs"; } } 21 | 22 | public List FileSystemCollection { get; set; } 23 | 24 | public RecentFileSystem(List RecentSongList) 25 | { 26 | _recentSongBaseList = RecentSongList; 27 | _recentSongList = new List(); 28 | _recentSongs = new List(); 29 | } 30 | 31 | public bool EntrySelected(ref int entryIndex) 32 | { 33 | //return true if we've found a song 34 | if (_recentSongs[entryIndex].EntryType.HasFlag(FileBrowserEntryType.Song)) return true; 35 | 36 | //something has gone wrong 37 | return false; 38 | } 39 | 40 | public int Initialise(FileBrowserEntry separator) 41 | { 42 | BuildRecentSongList(); 43 | return 0; 44 | } 45 | 46 | private void BuildRecentSongList() 47 | { 48 | //Build song list 49 | var _toRemove = new List(); 50 | _recentSongList.Clear(); 51 | foreach (var s in _recentSongBaseList) 52 | { 53 | var fs = FileSystemCollection.FirstOrDefault(f => f.FriendlyName.Equals(s.FileSystemFriendlyName, StringComparison.OrdinalIgnoreCase)); 54 | if (fs != null && fs.SongExists(s)) 55 | _recentSongList.Add(new Song { SongBase = s, FileSystem = fs }); 56 | else 57 | _toRemove.Add(s); 58 | 59 | } 60 | _toRemove.ForEach(s => _recentSongBaseList.Remove(s)); 61 | _recentSongs.Clear(); 62 | 63 | _recentSongs = _recentSongList.ConvertAll(s => new FileBrowserEntry { EntryType = FileBrowserEntryType.Song, Name = s.SongBase.Identifier, Path = s.SongBase.InternalName }); 64 | } 65 | 66 | public void LoadSongAudio(Song song) 67 | { 68 | // Sanity checks 69 | if (!File.Exists(song.SongBase.InternalName)) throw new Exception("File not found: " + song.SongBase.InternalName); 70 | 71 | song.SongAudio = CSCore.Codecs.CodecFactory.Instance.GetCodec(song.SongBase.InternalName); 72 | song.SongAudioLoaded = true; 73 | } 74 | 75 | public Song LoadSongInformation(int entryIndex) 76 | { 77 | return _recentSongList[entryIndex]; 78 | } 79 | 80 | public bool SongExists(SongBase song) 81 | { 82 | throw new NotImplementedException(); 83 | } 84 | 85 | public void Focused() 86 | { 87 | BuildRecentSongList(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/TurntNinja/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 | 55 | true 56 | 57 | 58 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | true 8 | $(MSBuildThisFileDirectory) 9 | $(MSBuildThisFileDirectory)..\ 10 | /Library/Frameworks/Mono.framework/Commands/mono 11 | mono 12 | 13 | 14 | 15 | $(PaketToolsPath)paket.exe 16 | $(PaketToolsPath)paket.bootstrapper.exe 17 | "$(PaketExePath)" 18 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 19 | "$(PaketBootStrapperExePath)" $(PaketBootStrapperCommandArgs) 20 | $(MonoPath) --runtime=v4.0.30319 $(PaketBootStrapperExePath) $(PaketBootStrapperCommandArgs) 21 | 22 | $(MSBuildProjectDirectory)\paket.references 23 | $(MSBuildStartupDirectory)\paket.references 24 | $(MSBuildProjectFullPath).paket.references 25 | $(PaketCommand) restore --references-files "$(PaketReferences)" 26 | $(PaketBootStrapperCommand) 27 | 28 | RestorePackages; $(BuildDependsOn); 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | [Bb]inaries/ 18 | 19 | # OpenTK Debug Binaries 20 | OpenTK Debug/ 21 | 22 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 23 | !packages/*/build/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | *_i.c 30 | *_p.c 31 | *.ilk 32 | *.meta 33 | *.obj 34 | *.pch 35 | *.pdb 36 | *.pgc 37 | *.pgd 38 | *.rsp 39 | *.sbr 40 | *.tlb 41 | *.tli 42 | *.tlh 43 | *.tmp 44 | *.tmp_proj 45 | *.log 46 | *.vspscc 47 | *.vssscc 48 | .builds 49 | *.pidb 50 | *.log 51 | *.scc 52 | 53 | # Visual C++ cache files 54 | ipch/ 55 | *.aps 56 | *.ncb 57 | *.opensdf 58 | *.sdf 59 | *.cachefile 60 | 61 | # Visual Studio profiler 62 | *.psess 63 | *.vsp 64 | *.vspx 65 | 66 | # Guidance Automation Toolkit 67 | *.gpState 68 | 69 | # ReSharper is a .NET coding add-in 70 | _ReSharper*/ 71 | *.[Rr]e[Ss]harper 72 | 73 | # TeamCity is a build add-in 74 | _TeamCity* 75 | 76 | # DotCover is a Code Coverage Tool 77 | *.dotCover 78 | 79 | # NCrunch 80 | *.ncrunch* 81 | .*crunch*.local.xml 82 | 83 | # Installshield output folder 84 | [Ee]xpress/ 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish/ 98 | 99 | # Publish Web Output 100 | *.Publish.xml 101 | 102 | # NuGet Packages Directory 103 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 104 | packages/ 105 | 106 | # Windows Azure Build Output 107 | csx 108 | *.build.csdef 109 | 110 | # Windows Store app package directory 111 | AppPackages/ 112 | 113 | # Others 114 | sql/ 115 | *.Cache 116 | ClientBin/ 117 | [Ss]tyle[Cc]op.* 118 | ~$* 119 | *~ 120 | *.dbmdl 121 | *.[Pp]ublish.xml 122 | *.pfx 123 | *.publishsettings 124 | 125 | # RIA/Silverlight projects 126 | Generated_Code/ 127 | 128 | # Backup & report files from converting an old project file to a newer 129 | # Visual Studio version. Backup files are not needed, because we have git ;-) 130 | _UpgradeReport_Files/ 131 | Backup*/ 132 | UpgradeLog*.XML 133 | UpgradeLog*.htm 134 | 135 | # SQL Server files 136 | App_Data/*.mdf 137 | App_Data/*.ldf 138 | 139 | 140 | #LightSwitch generated files 141 | GeneratedArtifacts/ 142 | _Pvt_Extensions/ 143 | ModelManifest.xml 144 | 145 | #Performance profiling files 146 | Performance Profiling/ 147 | *.nvuser 148 | 149 | #MonoDevelop 150 | *.userprefs 151 | 152 | # ========================= 153 | # Windows detritus 154 | # ========================= 155 | 156 | # Windows image file caches 157 | Thumbs.db 158 | ehthumbs.db 159 | 160 | # Folder config file 161 | Desktop.ini 162 | 163 | # Recycle Bin used on file shares 164 | $RECYCLE.BIN/ 165 | 166 | # Mac desktop service store files 167 | .DS_Store 168 | /BeatDetection.exe.config 169 | /BeatDetection.sln.DotSettings 170 | /BeatDetection.vshost.exe.config 171 | /Substructio/packages.config 172 | /CrashLogs 173 | 174 | # Sublime Text Stuff 175 | *.sublime* 176 | 177 | *.csv 178 | 179 | # Squirrel and autobuild/release stuff 180 | Releases/ 181 | SquirrelReleases/ 182 | *.rar 183 | 184 | # FAKE 185 | .fake/ 186 | 187 | # Build artifacts 188 | tmp/ 189 | 190 | # Deploy 191 | deploy/ 192 | 193 | # Paket 194 | paket.exe 195 | 196 | # vim 197 | *.swp 198 | 199 | # kickstart files 200 | kickstart.zip 201 | 202 | # butler 203 | butler 204 | butler.exe -------------------------------------------------------------------------------- /src/TurntNinja/Logging/SentryErrorReporting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Substructio.Logging; 7 | using SharpRaven; 8 | using SharpRaven.Data; 9 | using Substructio.Core; 10 | 11 | namespace TurntNinja.Logging 12 | { 13 | class SentryErrorReporting : IErrorReporting 14 | { 15 | readonly Dsn _sentryDsn; 16 | readonly string _userGUID; 17 | 18 | RavenClient _sentryClient; 19 | 20 | public SentryErrorReporting(string sentryURL, string environment, string version, string userGUID, Platform platform, string platformVersion, bool scrubUserName = false, 21 | Dictionary extraTags = null) 22 | { 23 | _sentryDsn = new Dsn(sentryURL); 24 | _userGUID = userGUID; 25 | InitialiseSentryClient(); 26 | 27 | _sentryClient.Environment = environment; 28 | _sentryClient.Release = version; 29 | _sentryClient.Tags["OS"] = $"{platform} {platformVersion}"; 30 | if (extraTags != null) 31 | AddTags(extraTags); 32 | 33 | if (scrubUserName) _sentryClient.LogScrubber = new SentryUserScrubber(); 34 | } 35 | 36 | private void InitialiseSentryClient() 37 | { 38 | _sentryClient = new RavenClient(_sentryDsn, new CustomJsonPacketFactory(), null, new SentryUserGUIDFactory(_userGUID)); 39 | } 40 | 41 | public void AddTags(IDictionary tags) 42 | { 43 | foreach (var t in tags) 44 | if (!_sentryClient.Tags.ContainsKey(t.Key)) _sentryClient.Tags.Add(t); 45 | } 46 | 47 | public string ReportError(Exception ex) 48 | { 49 | return ReportErrorAsync(ex).Result; 50 | } 51 | 52 | public string ReportMessage(string message) 53 | { 54 | return ReportMessageAsync(message).Result; 55 | } 56 | 57 | public Task ReportErrorAsync(Exception ex) 58 | { 59 | var sentryEvent = new SentryEvent(ex); 60 | return _sentryClient.CaptureAsync(sentryEvent); 61 | } 62 | 63 | public Task ReportMessageAsync(string message) 64 | { 65 | var sentryMessage = new SentryMessage(message); 66 | var sentryEvent = new SentryEvent(sentryMessage); 67 | return _sentryClient.CaptureAsync(sentryEvent); 68 | } 69 | } 70 | 71 | class CustomJsonPacketFactory : JsonPacketFactory 72 | { 73 | protected override JsonPacket OnCreate(JsonPacket jsonPacket) 74 | { 75 | // Scrub servername from the json packet since we don't need it 76 | jsonPacket.ServerName = ""; 77 | return jsonPacket; 78 | } 79 | } 80 | 81 | class SentryUserGUIDFactory : SentryUserFactory 82 | { 83 | readonly string _username; 84 | 85 | public SentryUserGUIDFactory(string username) 86 | { 87 | _username = username; 88 | } 89 | protected override SentryUser OnCreate(SentryUser user) 90 | { 91 | return new SentryUser(_username); 92 | } 93 | } 94 | 95 | class SentryUserScrubber : SharpRaven.Logging.IScrubber 96 | { 97 | public string Scrub(string input) 98 | { 99 | var search = "user\":{"; 100 | var start = input.IndexOf(search, StringComparison.Ordinal) + search.Length; 101 | var end = input.IndexOf("}", start, StringComparison.Ordinal); 102 | var ret = input.Substring(0, start) + input.Substring(end, input.Length - end); 103 | return ret; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/TurntNinja/Core/Settings/JsonSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Substructio.Core.Settings; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using System.IO; 10 | 11 | namespace TurntNinja.Core.Settings 12 | { 13 | class JsonSettings : IGameSettings 14 | { 15 | string _settingsFile; 16 | PropertySettings _propertySettings; 17 | Dictionary _defaultSettings; 18 | Dictionary _settings; 19 | bool _loaded; 20 | 21 | public JsonSettings(PropertySettings ps, string settingsFile) 22 | { 23 | _propertySettings = ps; 24 | _settingsFile = settingsFile; 25 | } 26 | public object this[string key] 27 | { 28 | get 29 | { 30 | if (!_loaded) throw new Exception("GameSettings was not loaded before access"); 31 | if (_settings.ContainsKey(key)) return _settings[key]; 32 | throw new GameSettingNotFoundException("The game setting with key {0} was not found", key); 33 | } 34 | set 35 | { 36 | if (_settings.ContainsKey(key)) _settings[key] = value; 37 | } 38 | } 39 | 40 | public void Load() 41 | { 42 | _defaultSettings = _propertySettings.GetAllSettings(); 43 | _settings = new Dictionary(); 44 | 45 | foreach (var kvp in _defaultSettings) 46 | { 47 | _settings.Add(kvp.Key, kvp.Value); 48 | } 49 | 50 | if (File.Exists(_settingsFile)) 51 | { 52 | var jsonSettings = JArray.Parse(File.ReadAllText(_settingsFile)); 53 | foreach (var jobj in jsonSettings) 54 | { 55 | var value = jobj["Value"].ToObject(); 56 | var name = jobj["Name"].ToObject(); 57 | var type = jobj["Type"].ToObject(); 58 | 59 | var valType = Type.GetType(type); 60 | var setting = JsonConvert.DeserializeObject(value, valType); 61 | 62 | // If this setting doesn't exist anymore, skip it 63 | if (!_settings.ContainsKey(name)) continue; 64 | 65 | _settings[name] = setting; 66 | } 67 | } 68 | _loaded = true; 69 | } 70 | 71 | public void Save() 72 | { 73 | var jsonSettings = new JArray(); 74 | foreach (var kvp in _settings) 75 | { 76 | // Skip this setting if it has not changed 77 | if (kvp.Value == _defaultSettings[kvp.Key]) continue; 78 | 79 | // Otherwise setting has changed, save it 80 | var seralized = JsonConvert.SerializeObject(kvp.Value); 81 | var jobj = new JObject(); 82 | jobj.Add("Value", seralized); 83 | jobj.Add(new JProperty("Name", kvp.Key)); 84 | jobj.Add(new JProperty("Type", kvp.Value.GetType().AssemblyQualifiedName)); 85 | jsonSettings.Add(jobj); 86 | } 87 | using (StreamWriter file = File.CreateText(_settingsFile)) 88 | using (JsonTextWriter writer = new JsonTextWriter(file)) 89 | { 90 | writer.Formatting = Formatting.Indented; 91 | jsonSettings.WriteTo(writer); 92 | } 93 | } 94 | 95 | public Dictionary GetAllSettings() 96 | { 97 | return new Dictionary(_settings); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/TurntNinja/Game/Difficulty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using OpenTK; 7 | 8 | namespace TurntNinja.Game 9 | { 10 | public class DifficultyOptions 11 | { 12 | public float Speed { get; private set; } 13 | public float VeryCloseDistance{ get; private set; } 14 | public float CloseDistance { get; private set; } 15 | public float RotationSpeed { get; private set; } 16 | public float BeatSkipDistance { get; private set; } 17 | public float DifficultyMultiplier { get; private set; } 18 | 19 | private const float MinSpeed = 400f; 20 | private const float MaxSpeed = 2000f; 21 | private const float SpeedMultiplier = 0.0025f*4; 22 | 23 | private const float MinVeryCloseDistance = 0.05f; 24 | private const float MaxVeryCloseDistance = 0.4f; 25 | private const float VeryCloseDistanceMultiplier = 0.4f*0.5f; 26 | 27 | private const float MinCloseDistance = 0.2f; 28 | private const float MaxCloseDistance = 0.6f; 29 | private const float CloseDistanceMultiplier = 0.6f*0.5f; 30 | 31 | private const float MinRotationSpeed = 0.0f; 32 | private const float MaxRotationSpeed = 4.0f; 33 | private const float RotationSpeedMultiplier = 0.5f*3; 34 | 35 | private const float MinBeatSkipDistance = 0.0f; 36 | private const float MaxBeatSkipDistance = 1.0f; 37 | 38 | public static DifficultyOptions Easy 39 | { 40 | get 41 | { 42 | return new DifficultyOptions(500f, 0.32f, 0.5f, 1.0f, 0.25f, 3.0f); 43 | } 44 | } 45 | 46 | public static DifficultyOptions Medium 47 | { 48 | get 49 | { 50 | return new DifficultyOptions(550f, 0.2f, 0.4f, 1.2f, 0.15f, 6.0f); 51 | } 52 | } 53 | 54 | public static DifficultyOptions Hard 55 | { 56 | get 57 | { 58 | return new DifficultyOptions(600f, 0.15f, 0.35f, 1.5f, 0.125f, 9.0f); 59 | } 60 | } 61 | 62 | public static DifficultyOptions Ultra 63 | { 64 | get 65 | { 66 | return new DifficultyOptions(700f, 0.15f, 0.30f, 1.9f, 0.0f, 12.0f); 67 | } 68 | } 69 | 70 | public static DifficultyOptions Ninja 71 | { 72 | get 73 | { 74 | return new DifficultyOptions(800f, 0.15f, 0.25f, 2.2f, 0.0f, 15.0f); 75 | } 76 | } 77 | 78 | private DifficultyOptions(float speed, float vCloseDistance, float closeDistance, float rotationSpeed, float beatSkipDistance, float multiplier) 79 | { 80 | Speed = MathHelper.Clamp(speed, MinSpeed, MaxSpeed); 81 | VeryCloseDistance = MathHelper.Clamp(vCloseDistance, MinVeryCloseDistance, MaxVeryCloseDistance); 82 | CloseDistance = MathHelper.Clamp(closeDistance, MinCloseDistance, MaxCloseDistance); 83 | RotationSpeed = MathHelper.Clamp(rotationSpeed, MinRotationSpeed, MaxRotationSpeed); 84 | BeatSkipDistance = MathHelper.Clamp(beatSkipDistance, MinBeatSkipDistance, MaxBeatSkipDistance); 85 | DifficultyMultiplier = multiplier; 86 | } 87 | 88 | public float GetScoreMultiplier() 89 | { 90 | return DifficultyMultiplier; 91 | //return (SpeedMultiplier*Speed) 92 | // + (VeryCloseDistanceMultiplier*(1/VeryCloseDistance)) 93 | // + (CloseDistanceMultiplier*(1/CloseDistance)) 94 | // + (RotationSpeedMultiplier*RotationSpeed); 95 | } 96 | } 97 | 98 | public enum DifficultyLevels 99 | { 100 | Easy, 101 | Medium, 102 | Hard, 103 | Ultra, 104 | Ninja 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/TurntNinja/Game/OnsetCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TurntNinja.Core; 7 | 8 | namespace TurntNinja.Game 9 | { 10 | class OnsetCollection 11 | { 12 | /// 13 | /// Collection of onset times 14 | /// 15 | public float[] OnsetTimes { get; private set; } 16 | 17 | /// 18 | /// Collection of beat frequencies 19 | /// 20 | public float[] BeatFrequencies { get; private set; } 21 | 22 | public float MaxBeatFrequency; 23 | public float MinBeatFrequency; 24 | 25 | /// 26 | /// Number of onsets 27 | /// 28 | public readonly int Count; 29 | 30 | /// 31 | /// Collection of pulse data 32 | /// 33 | public PulseData[] PulseDataCollection { get; private set; } 34 | 35 | /// 36 | /// How many onsets have been reached this frame 37 | /// 38 | public int OnsetsReached { get; private set; } 39 | 40 | /// 41 | /// Whether we need to start pulsing because we are near an onset 42 | /// 43 | public bool BeginPulsing { get; private set; } 44 | 45 | /// 46 | /// The index of the current onset 47 | /// 48 | public int OnsetIndex { get; private set; } = 0; 49 | 50 | /// 51 | /// Whether we are pulsing at the moment 52 | /// 53 | public bool Pulsing { get; private set; } 54 | 55 | /// 56 | /// The total elapsed game time for this 57 | /// 58 | public double ElapsedGameTime { get; private set; } 59 | 60 | private double _onsetTimeBuffer = 0.0f; 61 | 62 | public OnsetCollection(int onsetCount) 63 | { 64 | Count = onsetCount; 65 | OnsetTimes = new float[Count]; 66 | PulseDataCollection = new PulseData[Count]; 67 | } 68 | 69 | public void AddOnsets(float[] onsetTimes, float[] beatFrequencies) 70 | { 71 | OnsetTimes = onsetTimes; 72 | BeatFrequencies = beatFrequencies; 73 | MaxBeatFrequency = BeatFrequencies.Max(); 74 | MinBeatFrequency = BeatFrequencies.Min(); 75 | for (int i = 0; i < Count; i++) 76 | { 77 | PulseDataCollection[i] = new PulseData { 78 | PulseDirection = 1, 79 | PulseMultiplier = Math.Pow(BeatFrequencies[i] * 60, 1) + 70, 80 | PulseWidth = 0, 81 | PulseWidthMax = 25, 82 | Pulsing = false }; 83 | } 84 | } 85 | 86 | public void Update(double time) 87 | { 88 | ElapsedGameTime += time; 89 | 90 | OnsetIndex += OnsetsReached; 91 | if (BeginPulsing) 92 | { 93 | BeginPulsing = false; 94 | Pulsing = true; 95 | } 96 | if (OnsetsReached > 0) 97 | { 98 | BeginPulsing = false; 99 | Pulsing = false; 100 | } 101 | OnsetsReached = 0; 102 | BeginPulsing = false; 103 | 104 | for (int i = OnsetIndex; i < Count; i++) 105 | { 106 | //Check for any 'hit' beats 107 | if (OnsetTimes[i] <= ElapsedGameTime + _onsetTimeBuffer) OnsetsReached++; 108 | //Check if need to pulse center polygon 109 | else if (!Pulsing && CloseToNextOnset(i, PulseDataCollection[i].PulseWidthMax / PulseDataCollection[i].PulseMultiplier)) 110 | BeginPulsing = true; 111 | } 112 | } 113 | 114 | public bool CloseToNextOnset(int onsetIndex, double delta) 115 | { 116 | return (OnsetTimes[onsetIndex] - ElapsedGameTime) < delta; 117 | } 118 | 119 | public void Initialise() 120 | { 121 | OnsetIndex = 0; 122 | } 123 | 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/TurntNinja/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0 7 | 8 | 9 | 1 10 | 11 | 12 | 1280 13 | 14 | 15 | 720 16 | 17 | 18 | 4 19 | 20 | 21 | True 22 | 23 | 24 | Normal 25 | 26 | 27 | .mp3,.flac,.wav,.m4a,.wma 28 | 29 | 30 | turnt-ninja 31 | 32 | 33 | 2.5 34 | 35 | 36 | False 37 | 38 | 39 | 50 40 | 41 | 42 | 0 43 | 44 | 45 | False 46 | 47 | 48 | 0.2 49 | 50 | 51 | 10 52 | 53 | 54 | False 55 | 56 | 57 | True 58 | 59 | 60 | turnt-ninja.db 61 | 62 | 63 | opcon 64 | 65 | 66 | 67 | 68 | 69 | oeoywrZVrjYlcctPZZ3uTXFPp9ItVUkR3w28u+WDdd9SqYzE1PRkb+WPZsYQ+VvOfUjFQOlQ1tXc7oDXY4Km7A== 70 | 71 | 72 | jA9OAo1hWfHPOoMipHAAaFxFf2N7iP8zuBEI9LVQq5ET0vc8yqnLsO/oNMqCcki0rvD4CQrkMz7fWHQ4rWuHAyRfY+7nxGinxdbgIaceyVtKOJLsSnbBh4/NBq3OkBIAFG2GTxUhWqyJbxNhgzbu+0HM8jv43LY53bxhwIALt+A= 73 | 74 | 75 | False 76 | 77 | 78 | True 79 | 80 | 81 | 0 82 | 83 | 84 | False 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Vernon Adams (vern@newtypography.co.uk), with Reserved Font Names 'Oswald' 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /src/TurntNinja/Content/Fonts/Ostrich Sans/Open Font License.markdown: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Tyler Finck , with Reserved Font Name: "Ostrich Sans". 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | Version 1.1 - 26 February 2007 8 | 9 | 10 | SIL Open Font License 11 | ==================================================== 12 | 13 | 14 | Preamble 15 | ---------- 16 | 17 | The goals of the Open Font License (OFL) are to stimulate worldwide 18 | development of collaborative font projects, to support the font creation 19 | efforts of academic and linguistic communities, and to provide a free and 20 | open framework in which fonts may be shared and improved in partnership 21 | with others. 22 | 23 | The OFL allows the licensed fonts to be used, studied, modified and 24 | redistributed freely as long as they are not sold by themselves. The 25 | fonts, including any derivative works, can be bundled, embedded, 26 | redistributed and/or sold with any software provided that any reserved 27 | names are not used by derivative works. The fonts and derivatives, 28 | however, cannot be released under any other type of license. The 29 | requirement for fonts to remain under this license does not apply 30 | to any document created using the fonts or their derivatives. 31 | 32 | Definitions 33 | ------------- 34 | 35 | `"Font Software"` refers to the set of files released by the Copyright 36 | Holder(s) under this license and clearly marked as such. This may 37 | include source files, build scripts and documentation. 38 | 39 | `"Reserved Font Name"` refers to any names specified as such after the 40 | copyright statement(s). 41 | 42 | `"Original Version"` refers to the collection of Font Software components as 43 | distributed by the Copyright Holder(s). 44 | 45 | `"Modified Version"` refers to any derivative made by adding to, deleting, 46 | or substituting -- in part or in whole -- any of the components of the 47 | Original Version, by changing formats or by porting the Font Software to a 48 | new environment. 49 | 50 | `"Author"` refers to any designer, engineer, programmer, technical 51 | writer or other person who contributed to the Font Software. 52 | 53 | Permission & Conditions 54 | ------------------------ 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining 57 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 58 | redistribute, and sell modified and unmodified copies of the Font 59 | Software, subject to the following conditions: 60 | 61 | 1. Neither the Font Software nor any of its individual components, 62 | in Original or Modified Versions, may be sold by itself. 63 | 64 | 2. Original or Modified Versions of the Font Software may be bundled, 65 | redistributed and/or sold with any software, provided that each copy 66 | contains the above copyright notice and this license. These can be 67 | included either as stand-alone text files, human-readable headers or 68 | in the appropriate machine-readable metadata fields within text or 69 | binary files as long as those fields can be easily viewed by the user. 70 | 71 | 3. No Modified Version of the Font Software may use the Reserved Font 72 | Name(s) unless explicit written permission is granted by the corresponding 73 | Copyright Holder. This restriction only applies to the primary font name as 74 | presented to the users. 75 | 76 | 4. The name(s) of the Copyright Holder(s) or the Author(s) of the Font 77 | Software shall not be used to promote, endorse or advertise any 78 | Modified Version, except to acknowledge the contribution(s) of the 79 | Copyright Holder(s) and the Author(s) or with their explicit written 80 | permission. 81 | 82 | 5. The Font Software, modified or unmodified, in part or in whole, 83 | must be distributed entirely under this license, and must not be 84 | distributed under any other license. The requirement for fonts to 85 | remain under this license does not apply to any document created 86 | using the Font Software. 87 | 88 | Termination 89 | ----------- 90 | 91 | This license becomes null and void if any of the above conditions are 92 | not met. 93 | 94 | 95 | DISCLAIMER 96 | 97 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 98 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 99 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 100 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 101 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 102 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 103 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 104 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 105 | OTHER DEALINGS IN THE FONT SOFTWARE. 106 | -------------------------------------------------------------------------------- /src/TurntNinja/Logging/PiwikAnalytics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Substructio.Logging; 7 | using Substructio.Core; 8 | using Piwik.Tracker; 9 | 10 | namespace TurntNinja.Logging 11 | { 12 | class PiwikAnalytics : IAnalytics 13 | { 14 | readonly string _piwikURL; 15 | readonly int _appID; 16 | readonly Platform _platform; 17 | readonly string _platformVersion; 18 | readonly int _resX; 19 | readonly int _resY; 20 | readonly string _gameVersion; 21 | readonly string _userID; 22 | readonly string _baseURL; 23 | readonly Uri _baseUri; 24 | readonly string _userAgent; 25 | 26 | private PiwikTracker _piwikTracker; 27 | 28 | public PiwikAnalytics(int appID, string piwikURL, Platform platform, string platformVersion, int resX, int resY, string gameVersion, string userID, string baseURL) 29 | { 30 | _piwikURL = piwikURL; 31 | _appID = appID; 32 | _platform = platform; 33 | _platformVersion = platformVersion; 34 | _resX = resX; 35 | _resY = resY; 36 | _gameVersion = gameVersion; 37 | _userID = userID; 38 | _baseURL = baseURL; 39 | _baseUri = new Uri(_baseURL); 40 | 41 | // Initialize tracking client 42 | _piwikTracker = new PiwikTracker(_appID, _piwikURL); 43 | 44 | // Cookies don't exist without an HTTP Context, 45 | // but disable them explicitly for peace of mind 46 | _piwikTracker.disableCookieSupport(); 47 | 48 | // Set piwik timeout 49 | _piwikTracker.setRequestTimeout(10); 50 | 51 | // Set user agent 52 | _userAgent = BuildUserAgent(); 53 | _piwikTracker.setUserAgent(_userAgent); 54 | 55 | // Set resolution 56 | _piwikTracker.setResolution(_resX, _resY); 57 | 58 | // Set anonymous user id 59 | _piwikTracker.setUserId(_userID); 60 | 61 | // Force new visit on first tracking call, since we've just 62 | // started the application, i.e. a visit 63 | _piwikTracker.setForceNewVisit(); 64 | 65 | // Set default base url 66 | _piwikTracker.setUrl(_baseURL); 67 | } 68 | 69 | public void TrackApplicationStartup() 70 | { 71 | TrackApplicationView("startup", "Application Startup"); 72 | } 73 | 74 | public void TrackApplicationShutdown() 75 | { 76 | TrackApplicationView("shutdown", "Application Shutdown"); 77 | } 78 | 79 | public void TrackEvent(string eventCategory, string eventAction, string eventSubjectName = "", string eventValue = "") 80 | { 81 | try 82 | { 83 | // Set game version custom dimension for all tracking requests 84 | _piwikTracker.setCustomTrackingParameter("dimension1", _gameVersion); 85 | 86 | // Track the event 87 | var response = _piwikTracker.doTrackEvent(eventCategory, eventAction, eventSubjectName, eventValue); 88 | 89 | // Close response 90 | response.Close(); 91 | } 92 | catch {} 93 | } 94 | 95 | public void TrackApplicationView(string relativeURL, string title="") 96 | { 97 | try 98 | { 99 | // Set game version custom dimension for all tracking requests 100 | _piwikTracker.setCustomTrackingParameter("dimension1", _gameVersion); 101 | 102 | // Set page "url" 103 | _piwikTracker.setUrl(new Uri(_baseUri, relativeURL).ToString()); 104 | 105 | // Track the page view 106 | var response = _piwikTracker.doTrackPageView(title); 107 | 108 | // Close respone 109 | response.Close(); 110 | } 111 | catch {} 112 | } 113 | 114 | public void SetCustomVariable(int variableID, string variableName, string variableValue, CustomVariableScope variableScope) 115 | { 116 | try 117 | { 118 | var scope = (variableScope == CustomVariableScope.ApplicationLaunch) ? CustomVar.Scopes.visit : CustomVar.Scopes.page; 119 | _piwikTracker.setCustomVariable(variableID, variableName, variableValue, scope); 120 | } 121 | catch {} 122 | } 123 | 124 | private string BuildUserAgent() 125 | { 126 | var os = ""; 127 | switch (_platform) 128 | { 129 | case Platform.Windows: 130 | os = "Windows"; 131 | break; 132 | case Platform.Linux: 133 | os = "Linux"; 134 | break; 135 | case Platform.MacOSX: 136 | os = "Mac"; 137 | break; 138 | } 139 | return $"{os} {_platformVersion}"; 140 | } 141 | 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/TurntNinja/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | True 20 | 21 | 22 | 23 | 24 | True 25 | 26 | 27 | 28 | 29 | True 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 0 38 | 39 | 40 | 1 41 | 42 | 43 | 1280 44 | 45 | 46 | 720 47 | 48 | 49 | 4 50 | 51 | 52 | True 53 | 54 | 55 | Normal 56 | 57 | 58 | 2.5 59 | 60 | 61 | False 62 | 63 | 64 | 50 65 | 66 | 67 | 0 68 | 69 | 70 | False 71 | 72 | 73 | 0.2 74 | 75 | 76 | 10 77 | 78 | 79 | False 80 | 81 | 82 | True 83 | 84 | 85 | turnt-ninja.db 86 | 87 | 88 | opcon 89 | 90 | 91 | 92 | 93 | 94 | False 95 | 96 | 97 | True 98 | 99 | 100 | 0 101 | 102 | 103 | False 104 | 105 | 106 | 107 | 108 | 109 | 110 | .mp3,.flac,.wav,.m4a,.wma 111 | 112 | 113 | turnt-ninja 114 | 115 | 116 | oeoywrZVrjYlcctPZZ3uTXFPp9ItVUkR3w28u+WDdd9SqYzE1PRkb+WPZsYQ+VvOfUjFQOlQ1tXc7oDXY4Km7A== 117 | 118 | 119 | jA9OAo1hWfHPOoMipHAAaFxFf2N7iP8zuBEI9LVQq5ET0vc8yqnLsO/oNMqCcki0rvD4CQrkMz7fWHQ4rWuHAyRfY+7nxGinxdbgIaceyVtKOJLsSnbBh4/NBq3OkBIAFG2GTxUhWqyJbxNhgzbu+0HM8jv43LY53bxhwIALt+A= 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/TurntNinja/Audio/AudioFeatures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using OnsetDetection; 8 | using CSCore; 9 | using System.Diagnostics; 10 | 11 | namespace TurntNinja.Audio 12 | { 13 | class AudioFeatures 14 | { 15 | private OnsetDetector _onsetDetector; 16 | string _csvDirectory; 17 | string _outputSuffix = "onsets"; 18 | 19 | public List OnsetTimes = new List(); 20 | public List Onsets = new List(); 21 | public float _correction; 22 | private IProgress _outerProgressReporter; 23 | private IProgress _innerProgressReporter; 24 | private string _currentTask = ""; 25 | 26 | public AudioFeatures(DetectorOptions options, string csvDirectory, float correction, IProgress progress = null) 27 | { 28 | //force garbage collection 29 | GC.Collect(2, GCCollectionMode.Forced, true); 30 | 31 | _csvDirectory = csvDirectory; 32 | _outerProgressReporter = progress ?? new Progress(); 33 | _correction = correction; 34 | _innerProgressReporter = new Progress(status => 35 | { 36 | _outerProgressReporter.Report(_currentTask + ":" + status); 37 | }); 38 | 39 | _onsetDetector = new OnsetDetector(options, _innerProgressReporter); 40 | } 41 | 42 | public bool SongAnalysed(string audioPath) 43 | { 44 | return File.Exists(GetOnsetFilePath(audioPath)); 45 | } 46 | 47 | public void Extract(CSCore.IWaveSource audioSource, Song s) 48 | { 49 | _currentTask = "Analysing Song"; 50 | 51 | List onsets; 52 | if (SongAnalysed(s.SongBase.InternalName)) 53 | onsets = LoadOnsets(GetOnsetFilePath(s.SongBase.InternalName)); 54 | else 55 | { 56 | onsets = _onsetDetector.Detect(audioSource.ToSampleSource()); 57 | SaveOnsets(GetOnsetFilePath(s.SongBase.InternalName), onsets); 58 | } 59 | OnsetTimes = onsets.Select(o => o.OnsetTime).ToList(); 60 | Onsets = onsets; 61 | ApplyCorrection(OnsetTimes, _correction); 62 | 63 | //force garbage collection 64 | GC.Collect(2, GCCollectionMode.Forced, true); 65 | 66 | //audioSource.Position = 0; 67 | //AcoustID.ChromaContext cm = new AcoustID.ChromaContext(); 68 | //AcoustIDCSCore decoder = new AcoustIDCSCore(audioSource); 69 | //cm.Start(audioSource.WaveFormat.SampleRate, audioSource.WaveFormat.Channels); 70 | //decoder.Decode(cm.Consumer, 60); 71 | //cm.Finish(); 72 | 73 | //var f = cm.GetFingerprint(); 74 | 75 | //Debug.WriteLine(f); 76 | 77 | //AcoustID.Configuration.ClientKey = "3jSfGwVIGZ"; 78 | //AcoustID.Web.LookupService lService = new AcoustID.Web.LookupService(); 79 | //var duration = audioSource.GetLength().TotalSeconds; 80 | //var lookupTask = lService.GetAsync(f, (int)duration, new string[] { "recordings", "compress" }); 81 | //lookupTask.ContinueWith(t => 82 | //{ 83 | // var resp = t.Result; 84 | // foreach (var res in resp.Results) 85 | // { 86 | // Debug.WriteLine($"Score: {res.Score}, ID: {res.Id}"); 87 | // foreach (var rec in res.Recordings) 88 | // { 89 | // Debug.WriteLine($"{rec.Artists.First().Name} - {rec.Title}"); 90 | // } 91 | // } 92 | //}); 93 | //decoder.Dispose(); 94 | } 95 | 96 | 97 | private void SaveOnsets(string onsetFile, List onsets) 98 | { 99 | using (StreamWriter sw = new StreamWriter(onsetFile)) 100 | { 101 | foreach (var onset in onsets) 102 | { 103 | sw.WriteLine(onset.ToString()); 104 | } 105 | sw.Close(); 106 | } 107 | } 108 | 109 | private List LoadOnsets(string onsetFile) 110 | { 111 | List onsets = new List(); 112 | using (StreamReader sr = new StreamReader(onsetFile)) 113 | { 114 | string line; 115 | while ((line = sr.ReadLine()) != null) 116 | { 117 | onsets.Add(new Onset { OnsetTime = float.Parse(line.Split(',')[0]), OnsetAmplitude = float.Parse(line.Split(',')[1]) }); 118 | } 119 | sr.Close(); 120 | } 121 | return onsets; 122 | } 123 | 124 | private void ApplyCorrection(List onsets, float correction) 125 | { 126 | for (int i = 0; i < onsets.Count; i++) 127 | { 128 | onsets[i] += correction; 129 | } 130 | } 131 | 132 | private string GetOnsetFilePath(string audioPath) 133 | { 134 | if (!Directory.Exists(_csvDirectory)) Directory.CreateDirectory(_csvDirectory); 135 | return Path.Combine(_csvDirectory, String.Format("{0}_{1}", Path.GetFileNameWithoutExtension(audioPath), _outputSuffix) + ".csv"); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/TurntNinja/GUI/FirstRunScene.cs: -------------------------------------------------------------------------------- 1 | using Substructio.GUI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using QuickFont; 8 | using QuickFont.Configuration; 9 | using Substructio.Core; 10 | using OpenTK; 11 | using System.Drawing; 12 | using OpenTK.Graphics; 13 | using Substructio.Logging; 14 | using System.Diagnostics; 15 | 16 | namespace TurntNinja.GUI 17 | { 18 | class FirstRunScene : Scene 19 | { 20 | GameFont _bodyFont; 21 | GameFont _headerFont; 22 | QFontDrawing _fontDrawing; 23 | 24 | SizeF _headerSize; 25 | SizeF _bodyText1Size; 26 | SizeF _bodyListSize; 27 | SizeF _bodyText2Size; 28 | 29 | float _bodyTextWidth; 30 | 31 | string _headerText = "WELCOME TO TURNT NINJA"; 32 | 33 | string _bodyList = 34 | "* Operating system version\n" + 35 | "* Game resolution\n" + 36 | "* Number of songs played\n" + 37 | "* Crash reports\n"; 38 | 39 | string _bodyText1 = 40 | "Thank you for downloading my game!\nThis game has optional analytics " + 41 | "and crash reporting, which helps with development. The following "+ 42 | "data is collected if you opt in:\n"; 43 | 44 | string _bodyText2 = 45 | "\nPlease press Enter to opt in, or Escape to opt out.\n\nThank you,\nPatrick"; 46 | 47 | public FirstRunScene() 48 | { 49 | Exclusive = true; 50 | } 51 | 52 | public override void Load() 53 | { 54 | _bodyFont = SceneManager.GameFontLibrary.GetFirstOrDefault("largebody"); 55 | _headerFont = SceneManager.GameFontLibrary.GetFirstOrDefault(GameFontType.Menu); 56 | 57 | _fontDrawing = new QFontDrawing(); 58 | _fontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 59 | 60 | _headerSize = _headerFont.Font.Measure(_headerText); 61 | 62 | _bodyTextWidth = 0.75f; 63 | _bodyText1Size = _bodyFont.Font.Measure(_bodyText1, _bodyTextWidth * WindowWidth, QFontAlignment.Centre); 64 | _bodyText2Size = _bodyFont.Font.Measure(_bodyText2, _bodyTextWidth * WindowWidth, QFontAlignment.Centre); 65 | _bodyListSize = _bodyFont.Font.Measure(_bodyList, _bodyTextWidth * WindowWidth, QFontAlignment.Left); 66 | 67 | ServiceLocator.Settings["FirstRun"] = false; 68 | 69 | var informationalVersionAttribute = System.Reflection.Assembly.GetExecutingAssembly().CustomAttributes.FirstOrDefault(cad => cad.AttributeType == typeof(System.Reflection.AssemblyInformationalVersionAttribute)); 70 | string tag = ((string)informationalVersionAttribute.ConstructorArguments.First().Value).Split(' ')[0].Split(':')[1]; 71 | if (tag.Length > 1) 72 | ServiceLocator.Settings["GetAlphaReleases"] = true; 73 | 74 | Loaded = true; 75 | } 76 | 77 | public override void CallBack(GUICallbackEventArgs e) 78 | { 79 | } 80 | 81 | public override void Dispose() 82 | { 83 | _fontDrawing.Dispose(); 84 | } 85 | 86 | public override void Draw(double time) 87 | { 88 | _fontDrawing.RefreshBuffers(); 89 | _fontDrawing.Draw(); 90 | } 91 | 92 | public override void Resize(EventArgs e) 93 | { 94 | _fontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 95 | } 96 | 97 | public override void Update(double time, bool focused = false) 98 | { 99 | if (InputSystem.NewKeys.Contains(OpenTK.Input.Key.Enter)) 100 | { 101 | ServiceLocator.Settings["Analytics"] = true; 102 | ServiceLocator.Settings.Save(); 103 | 104 | // Track application startup 105 | ServiceLocator.Analytics.TrackApplicationStartup(); 106 | 107 | SceneManager.RemoveScene(this, true); 108 | } 109 | else if (InputSystem.NewKeys.Contains(OpenTK.Input.Key.Escape)) 110 | { 111 | ServiceLocator.Settings["Analytics"] = false; 112 | 113 | // Disable analytics and error reporting 114 | ServiceLocator.Analytics = new NullAnalytics(); 115 | ServiceLocator.ErrorReporting = new NullErrorReporting(); 116 | 117 | SceneManager.RemoveScene(this, true); 118 | } 119 | 120 | _fontDrawing.DrawingPrimitives.Clear(); 121 | 122 | var headerOffset = (WindowHeight/2.0f - WindowHeight/12.0f); 123 | _fontDrawing.Print(_headerFont.Font, _headerText, new Vector3(0, headerOffset + (_headerSize.Height / 2.0f), 0), QFontAlignment.Centre, Color.White); 124 | _fontDrawing.Print(_bodyFont.Font, _bodyText1, new Vector3(0, headerOffset - (_headerSize.Height), 0), new SizeF(WindowWidth * _bodyTextWidth, -1), QFontAlignment.Centre); 125 | _fontDrawing.Print(_bodyFont.Font, _bodyList, new Vector3(-WindowWidth*0.25f, (headerOffset - (_headerSize.Height)) - _bodyText1Size.Height, 0), new SizeF(WindowWidth * _bodyTextWidth, -1), QFontAlignment.Left); 126 | _fontDrawing.Print(_bodyFont.Font, _bodyText2, new Vector3(0, (headerOffset - (_headerSize.Height)) - _bodyText1Size.Height - _bodyListSize.Height, 0), new SizeF(WindowWidth * _bodyTextWidth, -1), QFontAlignment.Centre); 127 | } 128 | 129 | public override void EnterFocus() 130 | { 131 | } 132 | 133 | public override void ExitFocus() 134 | { 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/TurntNinja/GUI/ChooseSongScene.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TurntNinja.Core; 9 | using Substructio.Core; 10 | using Substructio.Graphics.OpenGL; 11 | using Substructio.GUI; 12 | using Key = OpenTK.Input.Key; 13 | using OpenTK; 14 | using OpenTK.Graphics; 15 | using TurntNinja.FileSystem; 16 | using TurntNinja.Audio; 17 | using System.Xml.Serialization; 18 | using Substructio; 19 | 20 | namespace TurntNinja.GUI 21 | { 22 | class ChooseSongScene : Scene 23 | { 24 | private GUIComponentContainer _guiComponents; 25 | private readonly PolarPolygon _centerPolygon; 26 | private readonly Player _player; 27 | private readonly ShaderProgram _shaderProgram; 28 | private List _recentSongs; 29 | 30 | private const string RECENT_SONGS_FILE = "recent-songs.xml"; 31 | private string _recentSongsFile = ""; 32 | 33 | DirectoryBrowser _directoryBrowser; 34 | 35 | public ChooseSongScene(GUIComponentContainer guiComponents, PolarPolygon centerPolygon, Player player, ShaderProgram shaderProgram) 36 | { 37 | _guiComponents = guiComponents; 38 | _centerPolygon = centerPolygon; 39 | _player = player; 40 | _shaderProgram = shaderProgram; 41 | Exclusive = true; 42 | } 43 | 44 | public override void Load() 45 | { 46 | _directoryBrowser = new DirectoryBrowser(SceneManager, this); 47 | _directoryBrowser.AddFileSystem(new LocalFileSystem(SceneManager.Directories)); 48 | _directoryBrowser.AddFileSystem(new SoundCloudFileSystem()); 49 | 50 | // Find recent songs file path 51 | _recentSongsFile = ServiceLocator.Directories.Locate("AppData", RECENT_SONGS_FILE); 52 | 53 | // Load recent songs 54 | LoadRecentSongs(); 55 | 56 | // Make sure to add recent file system last! 57 | _directoryBrowser.AddFileSystem(new RecentFileSystem(_recentSongs)); 58 | 59 | _directoryBrowser.SwitchCurrentFileSystemIfEmpty(); 60 | 61 | _directoryBrowser.Resize(WindowWidth, WindowHeight); 62 | Loaded = true; 63 | } 64 | 65 | private void LoadRecentSongs() 66 | { 67 | if (File.Exists(_recentSongsFile)) 68 | { 69 | var serializer = new XmlSerializer(typeof(List)); 70 | using (TextReader f = new StreamReader(_recentSongsFile)) 71 | { 72 | _recentSongs = (List)serializer.Deserialize(f); 73 | } 74 | } 75 | else 76 | _recentSongs = new List(); 77 | } 78 | 79 | private void SaveRecentSongs() 80 | { 81 | var serializer = new XmlSerializer(typeof(List)); 82 | using (TextWriter f = new StreamWriter(_recentSongsFile)) 83 | { 84 | serializer.Serialize(f, _recentSongs); 85 | } 86 | } 87 | 88 | public void SongChosen(Song song) 89 | { 90 | // Track song play with analytics 91 | ServiceLocator.Analytics.SetCustomVariable(1, "File System", song.FileSystem.FriendlyName, Substructio.Logging.CustomVariableScope.ApplicationView); 92 | ServiceLocator.Analytics.TrackEvent("Song", "Play", song.FileSystem.FriendlyName); 93 | 94 | // Remove the currently selected song if it already exists in the recent songs list 95 | // so that we can insert it again at the head, retaining the list order 96 | _recentSongs.Remove(song.SongBase); 97 | 98 | // Remove the oldest song if we are over the maximum recent song count 99 | if (_recentSongs.Count >= (int)SceneManager.GameSettings["MaxRecentSongCount"]) 100 | _recentSongs.RemoveAt(_recentSongs.Count - 1); 101 | 102 | // Insert the new song at the head of the list 103 | _recentSongs.Insert(0, song.SongBase); 104 | 105 | // Save recent songs file 106 | SaveRecentSongs(); 107 | 108 | // Refresh recent songs filesystem 109 | _directoryBrowser.RefreshRecentSongFilesystem(); 110 | 111 | //SceneManager.RemoveScene(this); 112 | this.Visible = false; 113 | SceneManager.AddScene( 114 | new LoadingScene( 115 | (float)SceneManager.GameSettings["AudioCorrection"], 116 | (float)SceneManager.GameSettings["MaxAudioVolume"], _centerPolygon, _player, _shaderProgram, song), this); 117 | } 118 | 119 | public override void CallBack(GUICallbackEventArgs e) 120 | { 121 | } 122 | 123 | public override void Resize(EventArgs e) 124 | { 125 | _directoryBrowser.Resize(WindowWidth, WindowHeight); 126 | } 127 | 128 | public override void Update(double time, bool focused = false) 129 | { 130 | if (focused) 131 | { 132 | if (InputSystem.NewKeys.Contains(Key.Escape)) this.Visible = false; 133 | 134 | _directoryBrowser.Update(time); 135 | } 136 | } 137 | 138 | public override void Draw(double time) 139 | { 140 | _directoryBrowser.Draw(time); 141 | } 142 | 143 | public override void Dispose() 144 | { 145 | } 146 | 147 | public override void EnterFocus() 148 | { 149 | InputSystem.RepeatingKeys.Add(Key.Down, KeyRepeatSettings.Default); 150 | InputSystem.RepeatingKeys.Add(Key.Up, KeyRepeatSettings.Default); 151 | InputSystem.RepeatingKeys.Add(Key.BackSpace, KeyRepeatSettings.Default); 152 | } 153 | 154 | public override void ExitFocus() 155 | { 156 | InputSystem.RepeatingKeys.Remove(Key.Down); 157 | InputSystem.RepeatingKeys.Remove(Key.Up); 158 | InputSystem.RepeatingKeys.Remove(Key.BackSpace); 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/TurntNinja/GUI/GameScene.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Drawing; 5 | using TurntNinja.Core; 6 | using TurntNinja.Game; 7 | using OpenTK; 8 | using OpenTK.Graphics; 9 | using OpenTK.Graphics.OpenGL4; 10 | using QuickFont; 11 | using Substructio.Graphics.OpenGL; 12 | using Substructio.GUI; 13 | using Gwen; 14 | using Substructio.Core; 15 | using Key = OpenTK.Input.Key; 16 | 17 | namespace TurntNinja.GUI 18 | { 19 | class GameScene : Scene 20 | { 21 | private Stage _stage; 22 | private Stage _backStage; 23 | private ProcessedText _multiplerText; 24 | public ShaderProgram ShaderProgram { get; set; } 25 | public bool UsingPlaylist { get; set; } 26 | public List PlaylistFiles { get; set; } 27 | 28 | private const float TIMETOWAIT = 1.0f; 29 | 30 | private double _elapsedTime = 0; 31 | 32 | public GameScene(Stage stage) 33 | { 34 | Exclusive = true; 35 | _stage = stage; 36 | //_stage.StageGeometry.UpdateColours(0); 37 | _stage.Initialise(); 38 | } 39 | 40 | public override void Load() 41 | { 42 | SceneManager.ScreenCamera.Scale = SceneManager.ScreenCamera.TargetScale = new Vector2(1.5f); 43 | if (UsingPlaylist) 44 | { 45 | 46 | } 47 | Loaded = true; 48 | } 49 | 50 | private void Exit(bool GoToEndScene = false) 51 | { 52 | _stage.Reset(!GoToEndScene); 53 | SceneManager.ScreenCamera.ExtraScale = 0; 54 | SceneManager.ScreenCamera.Scale = SceneManager.ScreenCamera.TargetScale = new Vector2(1, 1); 55 | SceneManager.GameWindow.Cursor = MouseCursor.Default; 56 | if (GoToEndScene) SceneManager.AddScene(new EndGameScene(_stage), this); 57 | SceneManager.RemoveScene(this, !GoToEndScene); 58 | } 59 | 60 | public override void CallBack(GUICallbackEventArgs e) 61 | { 62 | throw new NotImplementedException(); 63 | } 64 | 65 | public override void Resize(EventArgs e) 66 | { 67 | _stage.MultiplierFontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 68 | _stage.ScoreFontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 69 | } 70 | 71 | public override void Update(double time, bool focused = false) 72 | { 73 | if (InputSystem.NewKeys.Contains(Key.Escape)) 74 | { 75 | Exit(); 76 | return; 77 | } 78 | _stage.Update(time); 79 | 80 | if (_stage.Ended && (_stage.TotalTime - _stage.EndTime) > TIMETOWAIT) 81 | { 82 | Exit(true); 83 | } 84 | } 85 | 86 | public override void Draw(double time) 87 | { 88 | _elapsedTime += time; 89 | var rot = Matrix4.CreateRotationX((float)((MathHelper.PiOver4 / 1.25)*Math.Sin((_elapsedTime*0.20)))); 90 | ShaderProgram.Bind(); 91 | ShaderProgram.SetUniform("mvp", Matrix4.Mult(rot, SceneManager.ScreenCamera.WorldModelViewProjection)); 92 | _stage.Draw(time); 93 | //Cleanup the program 94 | ShaderProgram.UnBind(); 95 | 96 | if (SceneManager.Debug) 97 | { 98 | float yOffset = -SceneManager.Height * 0.5f + 30f; 99 | float xOffset = -SceneManager.Width * 0.5f + 20; 100 | xOffset += SceneManager.DrawTextLine(_stage.Overlap.ToString(), new Vector3(xOffset, yOffset, 0), Color.White, QFontAlignment.Left).Width + 20; 101 | xOffset += SceneManager.DrawTextLine(_stage.Hits.ToString(), new Vector3(xOffset, yOffset, 0), Color.Red, QFontAlignment.Left).Width + 20; 102 | xOffset += SceneManager.DrawTextLine(string.Format("{0}/{1}", _stage.CurrentPolygon, _stage.PolygonCount), new Vector3(xOffset, yOffset, 0), Color.White, QFontAlignment.Left).Width + 20; 103 | //xOffset += 104 | // SceneManager.Font.Print(string.Format("{0}/{1}", SceneManager.ScreenCamera.TargetScale, SceneManager.ScreenCamera.Scale), new Vector3(xOffset, yOffset, 0), QFontAlignment.Left, 105 | // Color.White).Width + 20; 106 | //xOffset += SceneManager.Font.Print(string.Format("Current score is {0}", _stage.StageGeometry.Player.Score), new Vector3(xOffset, yOffset, 0), QFontAlignment.Left, Color.White).Width + 20; 107 | //xOffset += SceneManager.Font.Print(string.Format("Scale is {0}", SceneManager.ScreenCamera.Scale), new Vector3(xOffset, yOffset, 0), QFontAlignment.Left, Color.White).Width + 20; 108 | //xOffset += SceneManager.Font.Print(string.Format("Pulse Multiplier is {0}", _stage.StageGeometry.CenterPolygon.PulseMultiplier), new Vector3(xOffset, yOffset, 0), QFontAlignment.Left, Color.White).Width + 20; 109 | xOffset += SceneManager.DrawTextLine(string.Format("Mouse coordinates are {0}", InputSystem.MouseXY), new Vector3(xOffset, yOffset, 0), Color.White, QFontAlignment.Left).Width + 20; 110 | xOffset += SceneManager.DrawTextLine(string.Format("Song Playing {0}", !_stage._stageAudio.IsStopped), new Vector3(xOffset, yOffset, 0), Color.White, QFontAlignment.Left).Width + 20; 111 | xOffset += SceneManager.DrawTextLine(string.Format("Beat Frequency {0}", _stage.StageGeometry.CurrentBeatFrequency), new Vector3(xOffset, yOffset, 0), Color.White, QFontAlignment.Left).Width + 20; 112 | 113 | yOffset = SceneManager.Height * 0.5f; 114 | xOffset = -SceneManager.Width * 0.5f + 20; 115 | yOffset -= SceneManager.DrawTextLine(_stage.StageGeometry.ColourModifiers.ToString(), new Vector3(xOffset, yOffset, 0), Color.White, QFontAlignment.Left).Height + 20; 116 | } 117 | } 118 | 119 | public override void Dispose() 120 | { 121 | _stage.Reset(true); 122 | _stage.Dispose(); 123 | _stage = null; 124 | } 125 | 126 | public override void EnterFocus() 127 | { 128 | } 129 | 130 | public override void ExitFocus() 131 | { 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/TurntNinja/Game/Player.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using ClipperLib; 5 | using OpenTK; 6 | using OpenTK.Graphics; 7 | using OpenTK.Input; 8 | using OpenTK.Graphics.OpenGL4; 9 | using Substructio.Core; 10 | using Substructio.Core.Math; 11 | using Substructio.Graphics.OpenGL; 12 | 13 | namespace TurntNinja 14 | { 15 | class Player 16 | { 17 | private double _length; 18 | private double _width; 19 | 20 | private PolarVector _position; 21 | private PolarVector _velocity; 22 | 23 | public int Hits; 24 | public Color4 Colour = Color4.White; 25 | 26 | private VertexBuffer _vertexBuffer; 27 | private VertexArray _vertexArray; 28 | private BufferDataSpecification _dataSpecification; 29 | 30 | public bool UseGamePad { get; set; } 31 | 32 | public ShaderProgram ShaderProgram 33 | { 34 | get { return _shaderProgram; } 35 | set 36 | { 37 | _shaderProgram = value; 38 | CreateBuffers(); 39 | } 40 | } 41 | 42 | public double Length 43 | { 44 | get { return _length; } 45 | } 46 | 47 | public double Width 48 | { 49 | get { return _width; } 50 | } 51 | 52 | public PolarVector Position 53 | { 54 | get { return _position; } 55 | set { _position = value; } 56 | } 57 | 58 | public int Direction { get; set; } 59 | 60 | public PolarVector Velocity 61 | { 62 | get { return _velocity; } 63 | } 64 | 65 | public bool IsSlow { get { return _currentFramesInput.HasFlag(Input.Slow); } } 66 | 67 | public float Score; 68 | 69 | private Input _currentFramesInput; 70 | private ShaderProgram _shaderProgram; 71 | 72 | const float PLAYER_RADIUS = 180f; 73 | const float PLAYER_WIDTH = 20; 74 | const float PLAYER_LENGTH_DEGREES = 10; 75 | const float SLOW_MULTIPLIER = 0.5f; 76 | 77 | public Player() 78 | { 79 | _length = MathHelper.DegreesToRadians(PLAYER_LENGTH_DEGREES); 80 | _width = PLAYER_WIDTH; 81 | _position = new PolarVector(1.5 * (Math.PI / 3) - _length * 0.5f, PLAYER_RADIUS); 82 | _velocity = new PolarVector(-9, 0); 83 | Direction = 1; 84 | UseGamePad = false; 85 | } 86 | 87 | public void Update(double time, bool AI = false) 88 | { 89 | if (!AI) _currentFramesInput = GetUserInput(); 90 | if (_currentFramesInput.HasFlag(Input.Slow)) 91 | time *= SLOW_MULTIPLIER; 92 | // _position.Azimuth += time*0.5*Direction; 93 | if (_currentFramesInput.HasFlag(Input.Left)) 94 | { 95 | _position.Azimuth -= _velocity.Azimuth*time; 96 | } 97 | else if (_currentFramesInput.HasFlag(Input.Right)) 98 | { 99 | _position.Azimuth += _velocity.Azimuth*time; 100 | } 101 | _position = _position.Normalised(); 102 | 103 | _vertexBuffer.Bind(); 104 | _vertexBuffer.Initialise(); 105 | _vertexBuffer.SetData(BuildVertexList(), _dataSpecification); 106 | _vertexBuffer.UnBind(); 107 | } 108 | 109 | private void CreateBuffers() 110 | { 111 | _dataSpecification = new BufferDataSpecification 112 | { 113 | Count = 2, 114 | Name = "in_position", 115 | Offset = 0, 116 | ShouldBeNormalised = false, 117 | Stride = 0, 118 | Type = VertexAttribPointerType.Float 119 | }; 120 | 121 | _vertexArray = new VertexArray{DrawPrimitiveType = PrimitiveType.Triangles}; 122 | _vertexArray.Bind(); 123 | 124 | var size = 3*2*sizeof (float); 125 | _vertexBuffer = new VertexBuffer 126 | { 127 | BufferUsage = BufferUsageHint.StreamDraw, 128 | DrawableIndices = 3, 129 | MaxSize = size 130 | }; 131 | _vertexBuffer.Bind(); 132 | _vertexBuffer.Initialise(); 133 | _vertexBuffer.DataSpecifications.Add(_dataSpecification); 134 | 135 | _vertexArray.Load(_shaderProgram, _vertexBuffer); 136 | _vertexArray.UnBind(); 137 | } 138 | 139 | public void Reset() 140 | { 141 | Score = 0; 142 | Hits = 0; 143 | _position = new PolarVector(1.5 * (Math.PI / 3) - _length * 0.5f, PLAYER_RADIUS); 144 | } 145 | 146 | public List GetBounds() 147 | { 148 | var p = new List(); 149 | var p1 = PolarVector.ToCartesianCoordinates(_position); 150 | var p2 = PolarVector.ToCartesianCoordinates(_position, _length/2, _width); 151 | var p3 = PolarVector.ToCartesianCoordinates(_position, _length, 0); 152 | 153 | p.Add(new IntPoint(p1.X, p1.Y)); 154 | p.Add(new IntPoint(p2.X, p2.Y)); 155 | p.Add(new IntPoint(p3.X, p3.Y)); 156 | 157 | return p; 158 | } 159 | 160 | public List BuildVertexList() 161 | { 162 | var verts = new List(); 163 | verts.Add(PolarVector.ToCartesianCoordinates(_position)); 164 | verts.Add(PolarVector.ToCartesianCoordinates(_position, _length/2, _width)); 165 | verts.Add(PolarVector.ToCartesianCoordinates(_position, _length, 0)); 166 | return verts.SelectMany(v => new[] {v.X, v.Y}).ToList(); 167 | } 168 | 169 | public void Draw(double time) 170 | { 171 | _vertexArray.Draw(time); 172 | } 173 | 174 | private Input GetUserInput() 175 | { 176 | Input i = Input.Default; 177 | //if (GamePad.GetCapabilities(0).IsConnected) 178 | //{ 179 | // if (OpenTK.Input.GamePad.GetState(0).Buttons.LeftShoulder == ButtonState.Pressed || GamePad.GetState(0).Triggers.Left > 0.3) 180 | // i |= Input.Left; 181 | // if (GamePad.GetState(0).Buttons.RightShoulder == ButtonState.Pressed || GamePad.GetState(0).Triggers.Right > 0.3) 182 | // i |= Input.Right; 183 | //} 184 | if (InputSystem.CurrentKeys.Contains(Key.Left)) 185 | i |= Input.Left; 186 | if (InputSystem.CurrentKeys.Contains(Key.Right)) 187 | i |= Input.Right; 188 | if (InputSystem.CurrentKeys.Contains(Key.Up)) 189 | i |= Input.Up; 190 | if (InputSystem.CurrentKeys.Contains(Key.Down)) 191 | i |= Input.Down; 192 | if (InputSystem.CurrentKeys.Contains(Key.ShiftLeft)) 193 | i |= Input.Slow; 194 | return i; 195 | } 196 | 197 | public void DoAI(double targetAzimuth) 198 | { 199 | _currentFramesInput = DirectionToTurn(_position.Azimuth, targetAzimuth); 200 | } 201 | 202 | private Input DirectionToTurn(double current, double target) 203 | { 204 | current = MathUtilities.Normalise(current, 0, MathUtilities.TwoPI); 205 | target = MathUtilities.Normalise(target, 0, MathUtilities.TwoPI); 206 | 207 | var diff = Math.Abs(current - target); 208 | if (diff < 0.1) 209 | return Input.Default; 210 | int flip = 1; 211 | if (diff > Math.PI) 212 | flip *= -1; 213 | int d = current > target ? -flip : flip; 214 | return d > 0 ? Input.Left : Input.Right; 215 | } 216 | } 217 | 218 | [Flags] 219 | public enum Input 220 | { 221 | Default = 0, 222 | Left = 1, 223 | Right = 2, 224 | Up = 4, 225 | Down = 8, 226 | Slow = 16 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /src/TurntNinja/FileSystem/SoundCloudFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TurntNinja.Audio; 8 | using SoundCloud.API.Client; 9 | using SoundCloud.API.Client.Objects; 10 | using System.Net; 11 | 12 | namespace TurntNinja.FileSystem 13 | { 14 | class SoundCloudFileSystem : IFileSystem 15 | { 16 | List _soundcloudSongs; 17 | List _scTracks; 18 | List _scCategories; 19 | FileBrowserEntry _entrySeparator; 20 | string clientID = "74e6e3acb28021e21eb32ef4bc10e995"; 21 | string clientSecret = ""; 22 | ISoundCloudConnector _sconnector; 23 | IUnauthorizedSoundCloudClient _scclient; 24 | 25 | const int FILE_SYSTEM_ENTRY_OFFSET = 2; 26 | 27 | public List FileSystemCollection { get; set; } 28 | public ReadOnlyCollection FileSystemEntryCollection { get { return _soundcloudSongs.AsReadOnly(); } } 29 | 30 | public string FriendlyName { get { return "Sound Cloud"; } } 31 | 32 | private object _lock = new object(); 33 | 34 | public SoundCloudFileSystem() 35 | { 36 | _soundcloudSongs = new List(); 37 | _scTracks = new List(); 38 | _scCategories = new List(); 39 | } 40 | 41 | public int Initialise(FileBrowserEntry separator) 42 | { 43 | _entrySeparator = separator; 44 | 45 | //setup soundcloud connection 46 | _sconnector = new SoundCloudConnector(); 47 | 48 | _scclient = _sconnector.UnauthorizedConnect(clientID, clientSecret); 49 | 50 | //Get tracks 51 | Task.Run(() => 52 | { 53 | lock (_lock ) 54 | { 55 | //_scCategories = _scclient.Explore.GetExploreCategories().ToList(); 56 | _scCategories = GetSoundcloudCategories(); 57 | ShowCategories(); 58 | } 59 | }); 60 | 61 | return 0; 62 | } 63 | 64 | public void ShowCategories() 65 | { 66 | _soundcloudSongs = _scCategories.ConvertAll(c => new FileBrowserEntry { EntryType = FileBrowserEntryType.Directory, Name = System.Uri.UnescapeDataString(c.Name).Replace('+',' '), Path = c.Name}); 67 | } 68 | 69 | public void ShowSongs() 70 | { 71 | _soundcloudSongs = _scTracks.ConvertAll(s => new FileBrowserEntry { EntryType = FileBrowserEntryType.Song, Name = s.Title, Path = s.Uri }); 72 | } 73 | 74 | public bool EntrySelected(ref int entryIndex) 75 | { 76 | //return true if we've found a song 77 | if (_soundcloudSongs[entryIndex].EntryType.HasFlag(FileBrowserEntryType.Song)) 78 | return true; 79 | 80 | //If category list selected 81 | if (_soundcloudSongs[entryIndex].EntryType.HasFlag(FileBrowserEntryType.Special)) 82 | { 83 | ShowCategories(); 84 | return false; 85 | } 86 | 87 | //If category selected 88 | _scTracks = _scclient.Chart.GetTracks(_scCategories[entryIndex]).ToList(); 89 | ShowSongs(); 90 | 91 | //Add the category list entry 92 | _soundcloudSongs.Insert(0, new FileBrowserEntry 93 | { 94 | EntryType = FileBrowserEntryType.Special, 95 | Name = "Category List", 96 | Path = "" 97 | }); 98 | 99 | _soundcloudSongs.Insert(1, _entrySeparator); 100 | 101 | entryIndex = 0; 102 | return false; 103 | } 104 | 105 | public void LoadSongAudio(Song song) 106 | { 107 | var sctrack = _scclient.Resolve.GetTrack(song.SongBase.InternalName); 108 | var url = sctrack.StreamUrl + "?client_id=" + clientID; 109 | var wr = WebRequest.Create(url); 110 | var response = wr.GetResponse(); 111 | song.SongAudio = CSCore.Codecs.CodecFactory.Instance.GetCodec(response.ResponseUri); 112 | } 113 | 114 | public Song LoadSongInformation(int entryIndex) 115 | { 116 | var sc = _scTracks[entryIndex - FILE_SYSTEM_ENTRY_OFFSET]; 117 | return new Song 118 | { 119 | FileSystem = this, 120 | SongBase = new SongBase 121 | { 122 | InternalName = sc.Uri, 123 | Artist = sc.User.UserName, 124 | Identifier = sc.Title, 125 | TrackName = sc.Title, 126 | FileSystemFriendlyName = FriendlyName, 127 | } 128 | }; 129 | } 130 | 131 | public bool SongExists(SongBase song) 132 | { 133 | //return _scclient.Resolve.GetTrack(song.InternalName) != null; 134 | return true; 135 | } 136 | 137 | public void Focused() 138 | { 139 | } 140 | 141 | public static List GetSoundcloudCategories() 142 | { 143 | return new List 144 | { 145 | new SCExploreCategory { Name = "Popular+Music" }, 146 | new SCExploreCategory { Name = "Alternative+Rock" }, 147 | new SCExploreCategory { Name = "Ambient" }, 148 | new SCExploreCategory { Name = "Classical" }, 149 | new SCExploreCategory { Name = "Country" }, 150 | new SCExploreCategory { Name = "Dance+&+EDM" }, 151 | new SCExploreCategory { Name = "Dancehall" }, 152 | new SCExploreCategory { Name = "Deep+House" }, 153 | new SCExploreCategory { Name = "Disco" }, 154 | new SCExploreCategory { Name = "Drum+&+Bass" }, 155 | new SCExploreCategory { Name = "Dubstep" }, 156 | new SCExploreCategory { Name = "Electronic" }, 157 | new SCExploreCategory { Name = "Folk+&+Singer-Songwriter" }, 158 | new SCExploreCategory { Name = "Hip-Hop+&+Rap" }, 159 | new SCExploreCategory { Name = "House" }, 160 | new SCExploreCategory { Name = "Indie" }, 161 | new SCExploreCategory { Name = "Jazz+&+Blues" }, 162 | new SCExploreCategory { Name = "Latin" }, 163 | new SCExploreCategory { Name = "Metal" }, 164 | new SCExploreCategory { Name = "Piano" }, 165 | new SCExploreCategory { Name = "Pop" }, 166 | new SCExploreCategory { Name = "R&B+&+soul" }, 167 | new SCExploreCategory { Name = "Reggae" }, 168 | new SCExploreCategory { Name = "Reggaeton" }, 169 | new SCExploreCategory { Name = "Rock" }, 170 | new SCExploreCategory { Name = "Soundtrack" }, 171 | new SCExploreCategory { Name = "Techno" }, 172 | new SCExploreCategory { Name = "Trance" }, 173 | new SCExploreCategory { Name = "Trap" }, 174 | new SCExploreCategory { Name = "Triphop" }, 175 | new SCExploreCategory { Name = "World" }, 176 | }; 177 | } 178 | 179 | internal void Search(string searchString) 180 | { 181 | _scTracks = _scclient.Tracks.BeginSearch(SoundCloud.API.Client.Objects.TrackPieces.SCFilter.All).Query(searchString).Exec().ToList(); 182 | 183 | ShowSongs(); 184 | 185 | //Add the category list entry 186 | _soundcloudSongs.Insert(0, new FileBrowserEntry 187 | { 188 | EntryType = FileBrowserEntryType.Special, 189 | Name = "Category List", 190 | Path = "" 191 | }); 192 | 193 | _soundcloudSongs.Insert(1, _entrySeparator); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/TurntNinja/GUI/EndGameScene.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using OpenTK; 8 | using OpenTK.Input; 9 | using QuickFont; 10 | using QuickFont.Configuration; 11 | using Substructio.Core; 12 | using Substructio.GUI; 13 | using TurntNinja.Game; 14 | using System.IO; 15 | using LiteDB; 16 | using System.Globalization; 17 | 18 | namespace TurntNinja.GUI 19 | { 20 | class EndGameScene : Scene 21 | { 22 | private GameFont _font; 23 | private QFontDrawing _fontDrawing; 24 | private SizeF _endGameTextSize; 25 | private string _endGameText = "Press Enter to Continue"; 26 | private Stage _stage; 27 | private HighScoreEntry _highScoreEntry; 28 | private bool _newHighScore = false; 29 | private PlayerScore _newScore; 30 | private PlayerScore _highestScore; 31 | 32 | public EndGameScene(Stage stage) 33 | { 34 | _stage = stage; 35 | Exclusive = true; 36 | } 37 | 38 | public override void Load() 39 | { 40 | _font = SceneManager.GameFontLibrary.GetFirstOrDefault("selected"); 41 | _fontDrawing = new QFontDrawing(); 42 | SceneManager.RemoveScene(ParentScene); 43 | SceneManager.ScreenCamera.ExtraScale = 0; 44 | SceneManager.ScreenCamera.Scale = SceneManager.ScreenCamera.TargetScale = new Vector2(1, 1); 45 | 46 | //save song to db 47 | string dbFile = Path.Combine(SceneManager.Directories["AppData"].FullName, (string)SceneManager.GameSettings["DatabaseFile"]); 48 | 49 | // Need to check for old version of database 50 | try 51 | { 52 | SaveHighScore(dbFile); 53 | } 54 | catch (Exception ex) 55 | { 56 | // this is an old version of the database, delete the file and try again 57 | try 58 | { 59 | File.Delete(dbFile); 60 | } 61 | catch (Exception) { } 62 | } 63 | 64 | // This shouldn't fail, because we checked for old version of the database 65 | SaveHighScore(dbFile); 66 | 67 | UpdateText(); 68 | Loaded = true; 69 | } 70 | 71 | private void SaveHighScore(string dbFile) 72 | { 73 | var cs = new ConnectionString(); 74 | cs.Filename = dbFile; 75 | cs.Upgrade = true; 76 | using (var db = new LiteDatabase(cs)) 77 | { 78 | var highSccoreCollection = db.GetCollection("highscores"); 79 | long hash = (long)Utilities.FNV1aHash64(Encoding.Default.GetBytes(_stage.CurrentSong.SongBase.InternalName)); 80 | 81 | //does this song exist in the database? 82 | if (highSccoreCollection.Exists(Query.And(Query.EQ("SongID", hash), Query.EQ("DifficultyLevel", _stage.CurrentDifficulty.ToString())))) 83 | { 84 | _highScoreEntry = highSccoreCollection.FindOne(Query.EQ("SongID", hash)); 85 | } 86 | else 87 | { 88 | _highScoreEntry = new HighScoreEntry { SongID = hash, SongName = _stage.CurrentSong.SongBase.Identifier, Difficulty = _stage.CurrentDifficulty }; 89 | } 90 | 91 | _highestScore = _highScoreEntry.HighScores.Count > 0 ? _highScoreEntry.HighScores.OrderByDescending(ps => ps.Score).First() : new PlayerScore(); 92 | _newScore = new PlayerScore 93 | { 94 | Name = (string)SceneManager.GameSettings["PlayerName"], 95 | Accuracy = 100 - ((float)_stage.Hits / _stage.StageGeometry.OnsetCount) * 100.0f, 96 | Score = (long)_stage.StageGeometry.Player.Score 97 | }; 98 | 99 | _highScoreEntry.HighScores.Add(_newScore); 100 | _highScoreEntry.HighScores.OrderByDescending(ps => ps.Score); 101 | 102 | // Save to DB 103 | using (var trans = db.BeginTrans()) 104 | { 105 | if (_highScoreEntry.Id == null || !highSccoreCollection.Update(_highScoreEntry)) highSccoreCollection.Insert(_highScoreEntry); 106 | } 107 | 108 | if (_newScore.Score > _highestScore.Score) 109 | { 110 | _highestScore = _newScore; 111 | _newHighScore = true; 112 | } 113 | } 114 | } 115 | 116 | public override void CallBack(GUICallbackEventArgs e) 117 | { 118 | } 119 | 120 | public override void Resize(EventArgs e) 121 | { 122 | UpdateText(); 123 | } 124 | 125 | private void UpdateText() 126 | { 127 | _fontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 128 | _fontDrawing.DrawingPrimitives.Clear(); 129 | _endGameTextSize = _font.Font.Measure(_endGameText); 130 | 131 | float fontOffset = 0; 132 | float endOffset = 0; 133 | 134 | if (_newHighScore) 135 | { 136 | fontOffset += _fontDrawing.Print(_font.Font, "New High Score", new Vector3(0, 2.0f * _endGameTextSize.Height, 0), QFontAlignment.Centre, Color.White).Height; 137 | fontOffset += _fontDrawing.Print(_font.Font, string.Format("Score: {0}", _highestScore.Score.ToString("N0", CultureInfo.CurrentCulture)), new Vector3(0, 0, 0), QFontAlignment.Centre, Color.White).Height; 138 | fontOffset += _fontDrawing.Print(_font.Font, string.Format("Accuracy: {0}%", _highestScore.Accuracy.ToString("#.##")), new Vector3(0, - _endGameTextSize.Height, 0), QFontAlignment.Centre, Color.White).Height; 139 | endOffset = -3.0f * _endGameTextSize.Height; 140 | } 141 | else 142 | { 143 | fontOffset += _fontDrawing.Print(_font.Font, string.Format("High Score: {0}", _highestScore.Score), new Vector3(0, 2.0f * _endGameTextSize.Height, 0), QFontAlignment.Centre, Color.White).Height; 144 | fontOffset += _fontDrawing.Print(_font.Font, string.Format("Score: {0}", _newScore.Score.ToString("N0", CultureInfo.CurrentCulture)), new Vector3(0, 0, 0), QFontAlignment.Centre, Color.White).Height; 145 | fontOffset += _fontDrawing.Print(_font.Font, string.Format("Accuracy: {0}%", _newScore.Accuracy.ToString("#.##")), new Vector3(0, -_endGameTextSize.Height, 0), QFontAlignment.Centre, Color.White).Height; 146 | endOffset = -3.0f * _endGameTextSize.Height; 147 | } 148 | _fontDrawing.Print(_font.Font, _endGameText, new Vector3(0, -(WindowHeight)/2.0f + _endGameTextSize.Height + 20, 0), QFontAlignment.Centre, Color.White); 149 | _fontDrawing.Print(_font.Font, string.Format("{0} - {1}", _stage.CurrentSong.SongBase.Identifier, _stage.CurrentDifficulty), new Vector3(0, (WindowHeight) / 2.0f - 20, 0), 150 | new SizeF(WindowWidth * 0.75f, -1), QFontAlignment.Centre, new QFontRenderOptions { Colour = Color.White }); 151 | _fontDrawing.RefreshBuffers(); 152 | } 153 | 154 | public override void Update(double time, bool focused = false) 155 | { 156 | if (InputSystem.NewKeys.Contains(Key.Enter) || InputSystem.NewKeys.Contains(Key.Escape) || InputSystem.NewKeys.Contains(Key.Space)) 157 | { 158 | _stage.StageGeometry.Player.Reset(); 159 | SceneManager.RemoveScene(this, true); 160 | } 161 | } 162 | 163 | public override void Draw(double time) 164 | { 165 | _fontDrawing.Draw(); 166 | } 167 | 168 | public override void Dispose() 169 | { 170 | _stage.Dispose(); 171 | _fontDrawing.Dispose(); 172 | } 173 | 174 | public override void EnterFocus() 175 | { 176 | } 177 | 178 | public override void ExitFocus() 179 | { 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/TurntNinja/GUI/LoadingScene.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using TurntNinja.Audio; 11 | using TurntNinja.Core; 12 | using TurntNinja.Game; 13 | using OpenTK; 14 | using OpenTK.Graphics; 15 | using QuickFont; 16 | using QuickFont.Configuration; 17 | using Substructio.Core.Math; 18 | using Substructio.Graphics.OpenGL; 19 | using Substructio.GUI; 20 | using OpenTK.Graphics.OpenGL4; 21 | using Substructio.Core; 22 | using System.Diagnostics; 23 | 24 | namespace TurntNinja.GUI 25 | { 26 | class LoadingScene : Scene 27 | { 28 | private float _audioCorrection; 29 | private float _maxAudioVolume; 30 | 31 | private Task _loadTask; 32 | private Stage _stage; 33 | private Player _player; 34 | private PolarPolygon _centerPolygon; 35 | private ProcessedText _loadingText; 36 | private ProcessedText _songText; 37 | private Vector3 _loadingTextPosition; 38 | private GameFont _loadingFont; 39 | private QFontDrawing _loadingFontDrawing; 40 | private QFontRenderOptions _loadingFontRenderOptions; 41 | private ShaderProgram _shaderProgram; 42 | private bool usePlaylist = false; 43 | private List _files = new List(); 44 | 45 | private Song _song; 46 | 47 | private string _loadingStatus = ""; 48 | 49 | public LoadingScene(float audioCorrection, float maxAudioVolume, PolarPolygon centerPolygon, Player player, ShaderProgram shaderProgram, Song song) 50 | { 51 | Exclusive = true; 52 | _audioCorrection = audioCorrection; 53 | _maxAudioVolume = maxAudioVolume; 54 | _centerPolygon = centerPolygon; 55 | _player = player; 56 | _shaderProgram = shaderProgram; 57 | 58 | _song = song; 59 | } 60 | 61 | public override void Load() 62 | { 63 | SceneManager.GameWindow.Cursor = MouseCursor.Empty; 64 | 65 | _stage = new Stage(this.SceneManager); 66 | _stage.ShaderProgram = _shaderProgram; 67 | 68 | _loadingFontRenderOptions = new QFontRenderOptions(); 69 | _loadingFontRenderOptions.DropShadowActive = true; 70 | _loadingFont = SceneManager.GameFontLibrary.GetFirstOrDefault(GameFontType.Heading); 71 | _loadingFontDrawing = new QFontDrawing(); 72 | _loadingFontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 73 | _loadingText = QFontDrawingPrimitive.ProcessText(_loadingFont.Font, _loadingFontRenderOptions, "Loading", new SizeF(200, -1), QFontAlignment.Centre); 74 | _loadingTextPosition = CalculateTextPosition(new Vector3((float)SceneManager.GameWindow.Width/ 2, SceneManager.GameWindow.Height/ 2, 0f), _loadingText); 75 | 76 | _songText = QFontDrawingPrimitive.ProcessText(_loadingFont.Font, _loadingFontRenderOptions, _song.SongBase.Identifier, new SizeF(SceneManager.GameWindow.Width - 40, -1), QFontAlignment.Centre); 77 | 78 | //Get difficulty options 79 | DifficultyOptions dOptions; 80 | switch ((DifficultyLevels)SceneManager.GameSettings["DifficultyLevel"]) 81 | { 82 | case DifficultyLevels.Easy: 83 | dOptions = DifficultyOptions.Easy; 84 | break; 85 | case DifficultyLevels.Medium: 86 | dOptions = DifficultyOptions.Medium; 87 | break; 88 | case DifficultyLevels.Hard: 89 | dOptions = DifficultyOptions.Hard; 90 | break; 91 | case DifficultyLevels.Ultra: 92 | dOptions = DifficultyOptions.Ultra; 93 | break; 94 | case DifficultyLevels.Ninja: 95 | dOptions = DifficultyOptions.Ninja; 96 | break; 97 | default: 98 | //shouldn't happen 99 | throw new Exception("Invalid difficulty level specified"); 100 | } 101 | 102 | var progress = new Progress(status => 103 | { 104 | _loadingStatus = status; 105 | }); 106 | _loadTask = Task.Factory.StartNew(() => _stage.LoadAsync(_song, _audioCorrection, _maxAudioVolume, progress, _centerPolygon, _player, dOptions, (DifficultyLevels)SceneManager.GameSettings["DifficultyLevel"])); 107 | 108 | Loaded = true; 109 | } 110 | 111 | public override void CallBack(GUICallbackEventArgs e) 112 | { 113 | throw new NotImplementedException(); 114 | } 115 | 116 | public override void Resize(EventArgs e) 117 | { 118 | _loadingText = QFontDrawingPrimitive.ProcessText(_loadingFont.Font, _loadingFontRenderOptions, "Loading", new SizeF(1000, -1), QFontAlignment.Centre); 119 | _loadingFontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.ScreenProjectionMatrix; 120 | _loadingTextPosition = CalculateTextPosition(new Vector3(SceneManager.ScreenCamera.PreferredWidth / 2, SceneManager.ScreenCamera.PreferredHeight / 2, 0f), _loadingText); 121 | } 122 | 123 | public override void Update(double time, bool focused = false) 124 | { 125 | if (_loadTask.Exception != null) 126 | { 127 | Trace.WriteLine(_loadTask.Exception.Message); 128 | throw _loadTask.Exception; 129 | } 130 | if (_loadTask.IsCompleted) 131 | { 132 | SceneManager.RemoveScene(this); 133 | SceneManager.AddScene(new GameScene(_stage){ShaderProgram = _shaderProgram, UsingPlaylist = usePlaylist, PlaylistFiles = _files}, this); 134 | } 135 | 136 | _player.Update(time); 137 | _centerPolygon.Update(time, false); 138 | //SceneManager.ScreenCamera.TargetScale += new Vector2(0.1f, 0.1f); 139 | } 140 | 141 | public override void Draw(double time) 142 | { 143 | GL.Disable(EnableCap.CullFace); 144 | _shaderProgram.Bind(); 145 | _shaderProgram.SetUniform("mvp", SceneManager.ScreenCamera.WorldModelViewProjection); 146 | _shaderProgram.SetUniform("in_color", Color4.White); 147 | 148 | //Draw the player 149 | _player.Draw(time); 150 | 151 | //Draw the center polygon 152 | _centerPolygon.Draw(time); 153 | 154 | _shaderProgram.SetUniform("in_color", Color4.Black); 155 | _centerPolygon.DrawOutline(time); 156 | 157 | //Cleanup the program 158 | _shaderProgram.UnBind(); 159 | 160 | _loadingFontDrawing.DrawingPrimitives.Clear(); 161 | float yOffset = 0; 162 | yOffset += _loadingFontDrawing.Print(_loadingFont.Font, _loadingText, _loadingTextPosition).Height; 163 | yOffset = MathHelper.Clamp(yOffset + 200 - 50*SceneManager.ScreenCamera.Scale.Y, yOffset, SceneManager.GameWindow.Height*0.5f); 164 | var pos = new Vector3(0, -yOffset, 0); 165 | yOffset += _loadingFontDrawing.Print(_loadingFont.Font, _songText, pos).Height; 166 | yOffset += _loadingFontDrawing.Print(_loadingFont.Font, _loadingStatus, new Vector3(0, -yOffset, 0), QFontAlignment.Centre).Height; 167 | _loadingFontDrawing.RefreshBuffers(); 168 | _loadingFontDrawing.Draw(); 169 | } 170 | 171 | public override void Dispose() 172 | { 173 | if (_loadingFontDrawing != null) 174 | { 175 | _loadingFontDrawing.Dispose(); 176 | } 177 | } 178 | 179 | private Vector3 CalculateTextPosition(Vector3 center, ProcessedText text) 180 | { 181 | var size = _loadingFont.Font.Measure(text); 182 | return new Vector3(0, size.Height/2, 0f); 183 | } 184 | 185 | public override void EnterFocus() 186 | { 187 | } 188 | 189 | public override void ExitFocus() 190 | { 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/TurntNinja/FileSystem/LocalFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TurntNinja.Audio; 9 | using System.Diagnostics; 10 | using Substructio.Core; 11 | 12 | namespace TurntNinja.FileSystem 13 | { 14 | class LocalFileSystem : IFileSystem 15 | { 16 | string[] _fileFilter; 17 | FileBrowserEntry _entrySeparator; 18 | List _drives; 19 | List _localFileSystemEntries; 20 | List _userFolders; 21 | IDirectoryHandler _directoryHandler; 22 | 23 | public ReadOnlyCollection FileSystemEntryCollection { get { return _localFileSystemEntries.AsReadOnly(); } } 24 | public List FileSystemCollection { get; set; } 25 | public string FriendlyName { get { return "Local Songs"; } } 26 | 27 | public LocalFileSystem(IDirectoryHandler directoryHandler) 28 | { 29 | _fileFilter = CSCore.Codecs.CodecFactory.Instance.GetSupportedFileExtensions(); 30 | _directoryHandler = directoryHandler; 31 | 32 | _drives = new List(); 33 | _localFileSystemEntries = new List(); 34 | _userFolders = new List(); 35 | } 36 | 37 | public int Initialise(FileBrowserEntry separator) 38 | { 39 | _entrySeparator = separator; 40 | 41 | // populate the drive list 42 | _drives.Clear(); 43 | _localFileSystemEntries.Clear(); 44 | _userFolders.Clear(); 45 | foreach (var driveInfo in DriveInfo.GetDrives().Where(d => d.IsReady)) 46 | { 47 | _drives.Add(new FileBrowserEntry 48 | { 49 | Path = driveInfo.RootDirectory.FullName, 50 | EntryType = FileBrowserEntryType.Directory | FileBrowserEntryType.Drive | FileBrowserEntryType.Special, 51 | Name = string.IsNullOrWhiteSpace(driveInfo.VolumeLabel) ? driveInfo.Name : string.Format("{1} ({0})", driveInfo.Name, driveInfo.VolumeLabel) 52 | }); 53 | } 54 | 55 | return EnterPath(_drives.First().Path); 56 | } 57 | 58 | public bool EntrySelected(ref int entryIndex) 59 | { 60 | var entry = _localFileSystemEntries[entryIndex]; 61 | 62 | // If a song has been selected, return immediately and notify the caller that the song is ready to be loaded 63 | if (entry.EntryType.HasFlag(FileBrowserEntryType.Song) && File.Exists(entry.Path)) return true; 64 | entryIndex = EnterPath(entry.Path); 65 | 66 | return false; 67 | } 68 | 69 | private int EnterPath(string directoryPath) 70 | { 71 | // Sanity check that directory exists 72 | if (!Directory.Exists(directoryPath)) throw new Exception("Directory doesn't exist: " + directoryPath); 73 | 74 | // Get a list of children in new directory 75 | var childrenDirectories = Directory.EnumerateDirectories(directoryPath).Where(d => !new DirectoryInfo(d).Attributes.HasFlag(FileAttributes.Hidden)); 76 | var childrenFiles = Directory.EnumerateFiles(directoryPath).Where(p => _fileFilter.Any(f => p.EndsWith(f, StringComparison.OrdinalIgnoreCase))); 77 | 78 | // Clear the file system entry list 79 | _localFileSystemEntries.Clear(); 80 | 81 | // Add the drive list 82 | _localFileSystemEntries.AddRange(_drives); 83 | 84 | // Add Drive/Directory separator 85 | _localFileSystemEntries.Add(_entrySeparator); 86 | 87 | // Add user folders 88 | _localFileSystemEntries.Add(new FileBrowserEntry 89 | { 90 | Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), 91 | EntryType = FileBrowserEntryType.Directory, 92 | Name = "My Documents" 93 | }); 94 | _localFileSystemEntries.Add(new FileBrowserEntry 95 | { 96 | Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), 97 | EntryType = FileBrowserEntryType.Directory, 98 | Name = "Desktop" 99 | }); 100 | _localFileSystemEntries.Add(new FileBrowserEntry 101 | { 102 | Path = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), 103 | EntryType = FileBrowserEntryType.Directory, 104 | Name = "My Music" 105 | }); 106 | 107 | _localFileSystemEntries.Add(new FileBrowserEntry 108 | { 109 | Path = _directoryHandler["BundledSongs"].FullName, 110 | EntryType = FileBrowserEntryType.Directory, 111 | Name = "Bundled Songs" 112 | }); 113 | 114 | _localFileSystemEntries.Add(_entrySeparator); 115 | 116 | // Add shortcut to parent directory 117 | _localFileSystemEntries.Add(new FileBrowserEntry 118 | { 119 | Path = Path.Combine(directoryPath, "../"), 120 | EntryType = FileBrowserEntryType.Directory | FileBrowserEntryType.Special, 121 | Name = "Back" 122 | }); 123 | 124 | _localFileSystemEntries.Add(_entrySeparator); 125 | 126 | int desiredIndex = _localFileSystemEntries.Count; 127 | 128 | // Add the new directories 129 | foreach (var dir in childrenDirectories.OrderBy(Path.GetFileName)) 130 | { 131 | _localFileSystemEntries.Add(new FileBrowserEntry 132 | { 133 | Path = dir, 134 | Name = Path.GetFileName(dir), 135 | EntryType = FileBrowserEntryType.Directory 136 | }); 137 | } 138 | 139 | // Add Directory/File separator 140 | _localFileSystemEntries.Add(_entrySeparator); 141 | 142 | // Add the new files 143 | foreach (var file in childrenFiles.OrderBy(Path.GetFileName)) 144 | { 145 | _localFileSystemEntries.Add(new FileBrowserEntry 146 | { 147 | Path = file, 148 | Name = Path.GetFileNameWithoutExtension(file), 149 | EntryType = FileBrowserEntryType.Song 150 | }); 151 | } 152 | 153 | // Update the index position 154 | if (_localFileSystemEntries.Count <= desiredIndex) desiredIndex = _localFileSystemEntries.Count - 1; 155 | 156 | return desiredIndex; 157 | } 158 | 159 | public Song LoadSongInformation(int entryIndex) 160 | { 161 | var entry = _localFileSystemEntries[entryIndex]; 162 | TagLib.Tag tag; 163 | 164 | // Load the tag information 165 | string artist = ""; 166 | string title = ""; 167 | 168 | if (TagLib.SupportedMimeType.AllExtensions.Any(s => entry.Path.EndsWith(s, StringComparison.OrdinalIgnoreCase))) 169 | { 170 | using (var fs = File.Open(entry.Path, FileMode.Open, FileAccess.Read, FileShare.Read)) 171 | { 172 | try 173 | { 174 | var file = TagLib.File.Create(new TagLib.StreamFileAbstraction(entry.Name, fs, fs)); 175 | tag = file.Tag; 176 | artist = tag.AlbumArtists.Count() > 0 ? tag.AlbumArtists[0] : ""; 177 | title = tag.Title; 178 | file.Dispose(); 179 | } 180 | catch (TagLib.UnsupportedFormatException ex) 181 | { 182 | } 183 | } 184 | } 185 | 186 | title = string.IsNullOrWhiteSpace(title) ? Path.GetFileNameWithoutExtension(entry.Path) : title; 187 | artist = string.IsNullOrWhiteSpace(title) ? "Unkown Artist" : artist; 188 | 189 | // Initialise the song variable 190 | var ret = new Song 191 | { 192 | FileSystem = this, 193 | SongBase = new SongBase 194 | { 195 | InternalName = entry.Path, 196 | Artist = artist, 197 | Identifier = entry.Name, 198 | TrackName = title, 199 | FileSystemFriendlyName = FriendlyName, 200 | } 201 | }; 202 | 203 | // Song information is loaded so we return it 204 | return ret; 205 | } 206 | 207 | public void LoadSongAudio(Song song) 208 | { 209 | // Sanity checks 210 | if (!File.Exists(song.SongBase.InternalName)) throw new Exception("File not found: " + song.SongBase.InternalName); 211 | 212 | song.SongAudio = CSCore.Codecs.CodecFactory.Instance.GetCodec(song.SongBase.InternalName); 213 | song.SongAudioLoaded = true; 214 | } 215 | 216 | public bool SongExists(SongBase song) 217 | { 218 | return File.Exists(song.InternalName); 219 | } 220 | 221 | public void Focused() 222 | { 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/TurntNinja/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | using TurntNinja.Core.Settings; 6 | using TurntNinja.Logging; 7 | using OpenTK; 8 | using OpenTK.Graphics; 9 | using Substructio.Core; 10 | using Substructio.IO; 11 | using System.Threading.Tasks; 12 | using System.Collections.Generic; 13 | 14 | namespace TurntNinja 15 | { 16 | public static class TurntNinjaGame 17 | { 18 | private static CrashReporter _crashReporter; 19 | 20 | [STAThread] 21 | public static void Main(string[] args) 22 | { 23 | System.Net.ServicePointManager.ServerCertificateValidationCallback = (s, ce, ch, p) => true; 24 | 25 | // Load services 26 | var initialSettingsProvider = new PropertySettings(); 27 | initialSettingsProvider.Load(); 28 | 29 | // Default settings provider is the Windows application settings implementation 30 | ServiceLocator.Settings = initialSettingsProvider; 31 | 32 | // DEBUG SETTINGS - Force first run 33 | //ServiceLocator.Settings["Analytics"] = false; 34 | //ServiceLocator.Settings["FirstRun"] = true; 35 | 36 | string PiwikURL = AESEncryption.Decrypt((string)ServiceLocator.Settings["Piwik"]); 37 | string SentryURL = AESEncryption.Decrypt((string)ServiceLocator.Settings["Sentry"]); 38 | 39 | #if DEBUG 40 | string sentryEnvironment = "debug"; 41 | #else 42 | string sentryEnvironment = "release"; 43 | #endif 44 | Version version = Assembly.GetExecutingAssembly().GetName().Version; 45 | string gameVersion = $"{version.Major}.{version.Minor}.{version.Build}"; 46 | var userID = (string)ServiceLocator.Settings["UserID"]; 47 | // Generate or load user ID for Piwik 48 | Guid userGUID; 49 | if (!Guid.TryParse(userID, out userGUID)) userGUID = Guid.NewGuid(); 50 | 51 | // Save user GUID 52 | ServiceLocator.Settings["UserID"] = userGUID.ToString(); 53 | 54 | Platform runningPlatform = PlatformDetection.RunningPlatform(); 55 | string platformVersion = PlatformDetection.GetVersionName(); 56 | 57 | // Load Sentry service 58 | if ((bool)ServiceLocator.Settings["Analytics"] || (bool)ServiceLocator.Settings["FirstRun"]) 59 | { 60 | ServiceLocator.ErrorReporting = new SentryErrorReporting( 61 | SentryURL, 62 | sentryEnvironment, 63 | gameVersion, 64 | userGUID.ToString(), 65 | runningPlatform, 66 | platformVersion); 67 | } 68 | 69 | int PiwikAppID = 3; 70 | 71 | // Load Piwik service 72 | if ((bool)ServiceLocator.Settings["Analytics"] || (bool)ServiceLocator.Settings["FirstRun"]) 73 | { 74 | ServiceLocator.Analytics = new PiwikAnalytics( 75 | PiwikAppID, 76 | PiwikURL, 77 | runningPlatform, 78 | platformVersion, 79 | DisplayDevice.Default.Width, 80 | DisplayDevice.Default.Height, 81 | gameVersion, 82 | userGUID.ToString(), 83 | "http://turntninja"); 84 | } 85 | 86 | ServiceLocator.Directories = new DirectoryHandler(); 87 | 88 | var directoryHandler = ServiceLocator.Directories; 89 | 90 | //set application path 91 | directoryHandler.AddPath("Application", AppDomain.CurrentDomain.BaseDirectory); 92 | //set base path 93 | if (Directory.Exists(directoryHandler.Locate("Application", "Content"))) 94 | directoryHandler.AddPath("Base", directoryHandler["Application"].FullName); 95 | else if (Directory.Exists(directoryHandler.Locate("Application", @"../../src/TurntNinja/Content"))) 96 | directoryHandler.AddPath("Base", directoryHandler.Locate("Application", @"../../src/TurntNinja")); 97 | else 98 | { 99 | throw new Exception("Couldn't find resource folder location"); 100 | } 101 | 102 | directoryHandler.AddPath("AppData", 103 | Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), (string)ServiceLocator.Settings["AppDataFolderName"])); 104 | 105 | directoryHandler.AddPath("Resources", Path.Combine(directoryHandler["Base"].FullName, "Content")); 106 | directoryHandler.AddPath("Fonts", Path.Combine(directoryHandler["Resources"].FullName, @"Fonts")); 107 | directoryHandler.AddPath("Shaders", Path.Combine(directoryHandler["Resources"].FullName, @"Shaders")); 108 | directoryHandler.AddPath("Images", Path.Combine(directoryHandler["Resources"].FullName, @"Images")); 109 | directoryHandler.AddPath("Crash", Path.Combine(directoryHandler["AppData"].FullName, "CrashLogs")); 110 | directoryHandler.AddPath("BundledSongs", Path.Combine(directoryHandler["Resources"].FullName, @"Songs")); 111 | directoryHandler.AddPath("ProcessedSongs", Path.Combine(directoryHandler["AppData"].FullName, @"Processed Songs")); 112 | 113 | if (!Directory.Exists(directoryHandler["AppData"].FullName)) Directory.CreateDirectory(directoryHandler["AppData"].FullName); 114 | 115 | if (!Debugger.IsAttached) 116 | { 117 | //initialise crash reporter 118 | _crashReporter = new CrashReporter(directoryHandler["Crash"].FullName); 119 | 120 | //attach exception handlers 121 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 122 | } 123 | 124 | // If we are not running on a windows platform, use the JsonSettings backend 125 | if (PlatformDetection.RunningPlatform() != Platform.Windows) 126 | { 127 | ServiceLocator.Settings = new JsonSettings(initialSettingsProvider, directoryHandler.Locate("AppData", "settings.json")); 128 | ServiceLocator.Settings.Load(); 129 | } 130 | 131 | //init logging 132 | Splat.DependencyResolverMixins.RegisterConstant(Splat.Locator.CurrentMutable, 133 | new SimpleLogger(directoryHandler["Application"].FullName, Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().Location) + ".log") 134 | { 135 | Level = Splat.LogLevel.Debug 136 | }, 137 | typeof(Splat.ILogger)); 138 | 139 | int rX = (int)ServiceLocator.Settings["ResolutionX"]; 140 | int rY = (int)ServiceLocator.Settings["ResolutionY"]; 141 | int FSAASamples = (int)ServiceLocator.Settings["AntiAliasingSamples"]; 142 | GraphicsMode graphicsMode = new GraphicsMode(32, 32, 8, FSAASamples, GraphicsMode.Default.AccumulatorFormat, 3); 143 | 144 | // Choose right OpenGL version for mac 145 | int major = 3; 146 | int minor = 0; 147 | if (PlatformDetection.RunningPlatform() == Platform.MacOSX) 148 | major = 4; 149 | 150 | if ((bool)ServiceLocator.Settings["Analytics"]) 151 | ServiceLocator.Analytics.TrackApplicationStartup(); 152 | 153 | using (GameController game = new GameController(ServiceLocator.Settings, rX, rY, graphicsMode, "Turnt Ninja", 154 | major, minor, directoryHandler)) 155 | { 156 | game.Title = "Turnt Ninja"; 157 | // Only set icon if we're on Windows or Linux 158 | if (runningPlatform != Platform.MacOSX) 159 | game.Icon = new System.Drawing.Icon(directoryHandler.Locate("Images", "icon.ico")); 160 | game.Run(); 161 | } 162 | } 163 | 164 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 165 | { 166 | try 167 | { 168 | Exception ex = (Exception)e.ExceptionObject; 169 | 170 | // Try to report exception 171 | try 172 | { 173 | string eventID = ServiceLocator.ErrorReporting.ReportError(ex); 174 | 175 | // Try to log crash ID 176 | try 177 | { 178 | ServiceLocator.Analytics.TrackEvent("Crash", "Fatal Crash", eventID); 179 | } 180 | catch { } 181 | } 182 | catch { } 183 | 184 | // Try to save crash report 185 | try 186 | { 187 | _crashReporter.LogError(ex); 188 | } 189 | catch { } 190 | 191 | // Try to report application shutdown 192 | try 193 | { 194 | ServiceLocator.Analytics.TrackApplicationShutdown(); 195 | } 196 | catch { } 197 | } 198 | finally 199 | { 200 | Environment.Exit(-1); 201 | } 202 | } 203 | 204 | public static string AssemblyDirectory 205 | { 206 | get 207 | { 208 | string codeBase = Assembly.GetExecutingAssembly().CodeBase; 209 | UriBuilder uri = new UriBuilder(codeBase); 210 | string path = Uri.UnescapeDataString(uri.Path); 211 | return Path.GetDirectoryName(path); 212 | } 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/TurntNinja/Game/StageAudio.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using CSCore; 8 | using CSCore.Codecs; 9 | using CSCore.CoreAudioAPI; 10 | using CSCore.SoundOut; 11 | using CSCore.Streams; 12 | using OpenTK; 13 | using Substructio.Core; 14 | 15 | 16 | namespace TurntNinja.Game 17 | { 18 | class StageAudio : IDisposable 19 | { 20 | public int AudioHashCode { get; private set; } 21 | private const int HashCount = 10000; 22 | //private WaveOut _waveOut; 23 | //private WaveStream _waveProvider; 24 | private float _maxVolume; 25 | 26 | private CancellationTokenSource _tokenSource = new CancellationTokenSource(); 27 | 28 | private IAudio _audio; 29 | 30 | /// 31 | /// Volume is clamped between 0 and MaxVolume 32 | /// 33 | public float Volume 34 | { 35 | get { return _audio.Volume; } 36 | set { _audio.Volume = MathHelper.Clamp(value, 0, 1); } 37 | } 38 | 39 | /// 40 | /// Max volume is between 0 and 1 41 | /// 42 | public float MaxVolume 43 | { 44 | get { return _maxVolume; } 45 | set { _maxVolume = MathHelper.Clamp(value, 0, 1); } 46 | } 47 | 48 | public bool IsStopped 49 | { 50 | get { return _audio.PlaybackState == PlaybackState.Stopped; } 51 | } 52 | 53 | public IWaveSource Source 54 | { 55 | get { return _audio.GetSource(); } 56 | } 57 | 58 | public void Load(IWaveSource source) 59 | { 60 | _audio = new CSCoreAudio(); 61 | _audio.Init(source); 62 | AudioHashCode = CRC16.Instance().ComputeChecksum(_audio.GetHashBytes(HashCount)); 63 | } 64 | 65 | public void Play() 66 | { 67 | _audio.Play(); 68 | } 69 | 70 | public void Pause() 71 | { 72 | _audio.Pause(); 73 | } 74 | 75 | public void Stop() 76 | { 77 | _audio.Stop(); 78 | _audio.Seek(0); 79 | } 80 | 81 | public void Resume() 82 | { 83 | _audio.Resume(); 84 | } 85 | 86 | public void Seek(float percent) 87 | { 88 | _audio.Seek(percent); 89 | } 90 | 91 | public string CreateTempWavFile(string audioFilePath, string tempFolderName = "") 92 | { 93 | var newFile = Path.Combine(Path.GetTempPath() + tempFolderName, Path.GetFileNameWithoutExtension(audioFilePath)) + ".wav"; 94 | 95 | //create directory if it doesn't exist 96 | var dir = Path.GetDirectoryName(newFile); 97 | if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); 98 | 99 | _audio.ConvertToWav(newFile); 100 | return newFile; 101 | } 102 | 103 | /// 104 | /// Fades out the audio. 105 | /// 106 | /// 107 | /// 108 | /// 109 | /// What action to do at the end of the fade 110 | public Task FadeOut(float time, float minVolume, float dVolume, FadeEndAction fadeEndAction) 111 | { 112 | var ct = _tokenSource.Token; 113 | return Task.Run(async () => 114 | { 115 | if (this.IsStopped) return; 116 | int dt = (int)(time / ((Volume - minVolume) / dVolume)); 117 | while (Volume > minVolume && !ct.IsCancellationRequested) 118 | { 119 | Volume -= dVolume; 120 | await Task.Delay(dt, ct); 121 | } 122 | switch (fadeEndAction) 123 | { 124 | case FadeEndAction.Pause: 125 | Pause(); 126 | break; 127 | case FadeEndAction.Stop: 128 | Stop(); 129 | break; 130 | } 131 | }, ct); 132 | } 133 | 134 | /// 135 | /// Fades in the audio 136 | /// 137 | /// Time over which to fade the audio in 138 | /// The maximum volume to reach 139 | /// The volume step size to use 140 | /// What action to do at the end of the fade 141 | public Task FadeIn(float time, float maxVolume, float dVolume, FadeEndAction fadeEndAction) 142 | { 143 | if (maxVolume > MaxVolume) throw new ArgumentOutOfRangeException("The maximum fade in volume " + maxVolume + " was greater than the maximum volume for this audio " + MaxVolume); 144 | var ct = _tokenSource.Token; 145 | return Task.Run(async () => 146 | { 147 | int dt = (int)(time / ((maxVolume - Volume) / dVolume)); 148 | while (Volume < maxVolume && !ct.IsCancellationRequested) 149 | { 150 | Volume += dVolume; 151 | await Task.Delay(dt, ct); 152 | } 153 | switch (fadeEndAction) 154 | { 155 | case FadeEndAction.Pause: 156 | Pause(); 157 | break; 158 | case FadeEndAction.Stop: 159 | Stop(); 160 | break; 161 | } 162 | }, ct); 163 | } 164 | 165 | public void CancelAudioFades() 166 | { 167 | _tokenSource.Cancel(); 168 | _tokenSource = new CancellationTokenSource(); 169 | } 170 | 171 | public void Dispose() 172 | { 173 | if (_audio != null) _audio.Dispose(); 174 | } 175 | } 176 | 177 | 178 | internal interface IAudio : IDisposable 179 | { 180 | PlaybackState PlaybackState { get; } 181 | float Volume { get; set; } 182 | //void Init(string audioFilePath); 183 | void Init(IWaveSource source); 184 | byte[] GetHashBytes(int hashByteCount); 185 | void Play(); 186 | void Pause(); 187 | void Resume(); 188 | void Stop(); 189 | void Seek(float percent); 190 | void ConvertToWav(string wavFilePath); 191 | IWaveSource GetSource(); 192 | } 193 | 194 | enum FadeEndAction 195 | { 196 | Nothing, 197 | Pause, 198 | Stop, 199 | } 200 | 201 | enum PlaybackState 202 | { 203 | Paused, 204 | Playing, 205 | Stopped 206 | } 207 | 208 | class CSCoreAudio : IAudio 209 | { 210 | private ISoundOut _soundOut; 211 | private IWaveSource _soundSource; 212 | 213 | public void Dispose() 214 | { 215 | if (_soundOut != null) 216 | { 217 | _soundOut.Stop(); 218 | _soundOut.Dispose(); 219 | if (_soundOut is ALSoundOut) 220 | { 221 | CSCore.SoundOut.AL.ALDevice.DefaultDevice.Dispose(); 222 | } 223 | _soundOut = null; 224 | } 225 | if (_soundSource != null) 226 | { 227 | _soundSource.Dispose(); 228 | _soundSource = null; 229 | } 230 | } 231 | 232 | public PlaybackState PlaybackState 233 | { 234 | get 235 | { 236 | switch (_soundOut.PlaybackState) 237 | { 238 | case CSCore.SoundOut.PlaybackState.Paused: 239 | return PlaybackState.Paused; 240 | case CSCore.SoundOut.PlaybackState.Playing: 241 | return PlaybackState.Playing; 242 | case CSCore.SoundOut.PlaybackState.Stopped: 243 | return PlaybackState.Stopped; 244 | } 245 | return PlaybackState.Stopped; 246 | } 247 | } 248 | 249 | public float Volume { get { return _soundOut.Volume; } set { _soundOut.Volume = value; } } 250 | 251 | public IWaveSource GetSource() 252 | { 253 | return _soundSource; 254 | } 255 | 256 | //public void Init(string audioFilePath) 257 | //{ 258 | // Init(CodecFactory.Instance.GetCodec(audioFilePath)); 259 | //} 260 | 261 | public void Init(IWaveSource source) 262 | { 263 | _soundSource = source; 264 | // Use ALSound out if we are running on Mac/Linux 265 | _soundOut = (PlatformDetection.RunningPlatform() == Platform.Windows) ? 266 | (ISoundOut) new WasapiOut() : new ALSoundOut(); 267 | _soundOut.Initialize(_soundSource); 268 | _soundOut.Stopped += (sender, args) => { if (args.HasError) throw new Exception("exception thrown on stoping audio", args.Exception); }; 269 | } 270 | 271 | public byte[] GetHashBytes(int hashByteCount) 272 | { 273 | var ret = new byte[hashByteCount]; 274 | _soundSource.Read(ret, 0, hashByteCount); 275 | _soundSource.Position = 0; 276 | return ret; 277 | } 278 | 279 | 280 | public void Play() 281 | { 282 | _soundOut.Play(); 283 | } 284 | 285 | public void Resume() 286 | { 287 | _soundOut.Resume(); 288 | } 289 | 290 | public void Stop() 291 | { 292 | _soundOut.Stop(); 293 | _soundSource.SetPosition(TimeSpan.Zero); 294 | } 295 | 296 | public void Seek(float percent) 297 | { 298 | _soundSource.SetPosition(TimeSpan.FromMilliseconds(percent * _soundSource.GetLength().TotalMilliseconds)); 299 | } 300 | 301 | public void ConvertToWav(string wavFilePath) 302 | { 303 | _soundSource.WriteToFile(wavFilePath); 304 | } 305 | 306 | public void Pause() 307 | { 308 | _soundOut.Pause(); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/TurntNinja/GUI/MenuScene.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TurntNinja.Core; 9 | using Gwen.Skin; 10 | using OpenTK; 11 | using OpenTK.Graphics; 12 | using OpenTK.Input; 13 | using QuickFont; 14 | using QuickFont.Configuration; 15 | using Substructio.Core; 16 | using Substructio.Core.Math; 17 | using Substructio.Graphics.OpenGL; 18 | using Substructio.GUI; 19 | 20 | namespace TurntNinja.GUI 21 | { 22 | class MenuScene : Scene 23 | { 24 | private ShaderProgram _shaderProgram; 25 | private Player _player; 26 | 27 | private PolarPolygon _centerPolygon; 28 | 29 | private string _selectedMenuItemText = ""; 30 | private MainMenuOptions _selectedMenuItem = MainMenuOptions.None; 31 | private bool _selectedItemChanged; 32 | 33 | private GameFont _menuFont; 34 | private GameFont _versionFont; 35 | private QFontDrawing _menuFontDrawing; 36 | private QFontRenderOptions _menuRenderOptions; 37 | private QFontDrawingPrimitive _menuFDP; 38 | 39 | private GUIComponentContainer _GUIComponents; 40 | 41 | private double _totalTime; 42 | 43 | private string _gameVersion; 44 | 45 | public override void Load() 46 | { 47 | SceneManager.GameWindow.Cursor = MouseCursor.Default; 48 | 49 | // Remap keypad enter to normal enter 50 | InputSystem.KeyRemappings.Add(Key.KeypadEnter, Key.Enter); 51 | 52 | _gameVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); 53 | 54 | // Choose correct version directive because OSX is dumb 55 | string version = "#version 130"; 56 | if (PlatformDetection.RunningPlatform() == Platform.MacOSX) 57 | version = "#version 150"; 58 | 59 | // Load shaders 60 | var vert = new Shader(Path.Combine(SceneManager.Directories["Shaders"].FullName, "simple.vs"), version); 61 | var frag = new Shader(Path.Combine(SceneManager.Directories["Shaders"].FullName, "simple.fs"), version); 62 | _shaderProgram = new ShaderProgram(); 63 | _shaderProgram.Load(vert, frag); 64 | 65 | _player = new Player(); 66 | _player.Position = new PolarVector(1.5 * (Math.PI / 3) - _player.Length * 0.5f, _player.Position.Radius); 67 | _player.ShaderProgram = _shaderProgram; 68 | _centerPolygon = new PolarPolygon(Enumerable.Repeat(true, 6).ToList(), new PolarVector(0.5, 0), 50, 80, 0); 69 | _centerPolygon.ShaderProgram = _shaderProgram; 70 | 71 | _menuFont = SceneManager.GameFontLibrary.GetFirstOrDefault("menuworld"); 72 | _menuFontDrawing = new QFontDrawing(); 73 | _menuFontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.WorldModelViewProjection; 74 | _menuRenderOptions = new QFontRenderOptions { DropShadowActive = true, Colour = Color.White }; 75 | 76 | _versionFont = SceneManager.GameFontLibrary.GetFirstOrDefault("versiontext"); 77 | 78 | var guiRenderer = new Gwen.Renderer.OpenTK(); 79 | var skin = new TexturedBase(guiRenderer, Path.Combine(SceneManager.Directories["Images"].FullName, "DefaultSkin.png")); 80 | skin.DefaultFont = new Gwen.Font(guiRenderer, SceneManager.FontPath, 30); 81 | _GUIComponents = new GUIComponentContainer(guiRenderer, skin); 82 | 83 | Loaded = true; 84 | } 85 | 86 | public override void CallBack(GUICallbackEventArgs e) 87 | { 88 | } 89 | 90 | public override void Resize(EventArgs e) 91 | { 92 | _GUIComponents.Resize(SceneManager.ScreenCamera.ScreenProjectionMatrix, WindowWidth, WindowHeight); 93 | _menuFontDrawing.ProjectionMatrix = SceneManager.ScreenCamera.WorldModelViewProjection; 94 | _selectedItemChanged = true; 95 | } 96 | 97 | public override void Update(double time, bool focused = false) 98 | { 99 | // Update total elapsed time 100 | _totalTime += time; 101 | 102 | if (InputSystem.NewKeys.Contains(Key.Escape)) Exit(); 103 | 104 | _player.Update(time); 105 | _centerPolygon.Update(time, false); 106 | 107 | DoGUI(); 108 | 109 | // Update next if needed 110 | if (_selectedItemChanged) 111 | { 112 | // Reset elapsed time 113 | _totalTime = 0; 114 | 115 | _menuFontDrawing.DrawingPrimitives.Clear(); 116 | _menuFDP = new QFontDrawingPrimitive(_menuFont.Font, _menuRenderOptions); 117 | 118 | 119 | _menuFDP.Print(_selectedMenuItemText.ToUpper(), Vector3.Zero, QFontAlignment.Centre); 120 | _menuFontDrawing.DrawingPrimitives.Add(_menuFDP); 121 | _selectedItemChanged = false; 122 | } 123 | 124 | // Pulse text 125 | var size = _menuFont.Font.Measure(_selectedMenuItemText.ToUpper()); 126 | var selectedSide = GetSelectedSide(); 127 | var newPos = new PolarVector(selectedSide * _centerPolygon.AngleBetweenSides + _centerPolygon.AngleBetweenSides * 0.5f, _player.Position.Radius + _player.Width + size.Height*0.9); 128 | 129 | var extraRotation = (selectedSide >= 0 && selectedSide < 3) ? (-Math.PI / 2.0) : (Math.PI / 2.0); 130 | var extraOffset = (selectedSide >= 0 && selectedSide < 3) ? (0) : (-size.Height / 4); 131 | 132 | newPos.Radius += extraOffset; 133 | var cart = newPos.ToCartesianCoordinates(); 134 | var mvm = Matrix4.CreateTranslation(0, size.Height / 2, 0) 135 | * Matrix4.CreateScale(0.90f + (float)Math.Pow(Math.Sin(_totalTime*3), 2)*0.10f) 136 | * Matrix4.CreateRotationZ((float)(newPos.Azimuth + extraRotation)) 137 | * Matrix4.CreateTranslation(cart.X, cart.Y, 0); 138 | _menuFDP.ModelViewMatrix = mvm; 139 | } 140 | 141 | public void Exit() 142 | { 143 | SceneManager.Exit(); 144 | } 145 | 146 | private int GetSelectedSide() 147 | { 148 | var nTheta = MathUtilities.Normalise(_player.Position.Azimuth + _player.Length * 0.5f); 149 | return (int)Math.Floor(nTheta / _centerPolygon.AngleBetweenSides); 150 | } 151 | 152 | private void DoGUI() 153 | { 154 | int selectedSide = GetSelectedSide(); 155 | if (_selectedMenuItem != (MainMenuOptions) selectedSide) _selectedItemChanged = true; 156 | _selectedMenuItem = (MainMenuOptions) selectedSide; 157 | 158 | switch (_selectedMenuItem) 159 | { 160 | case MainMenuOptions.SinglePlayer: 161 | _selectedMenuItemText = "Play"; 162 | break; 163 | case MainMenuOptions.Scores: 164 | _selectedMenuItemText = "Scores"; 165 | break; 166 | case MainMenuOptions.Options: 167 | _selectedMenuItemText = "Options"; 168 | break; 169 | case MainMenuOptions.Exit: 170 | _selectedMenuItemText = "Exit"; 171 | break; 172 | case MainMenuOptions.Update: 173 | _selectedMenuItemText = "Update"; 174 | break; 175 | case MainMenuOptions.ComingSoon: 176 | _selectedMenuItemText = "Coming Soon"; 177 | break; 178 | default: 179 | _selectedMenuItem = MainMenuOptions.None; 180 | _selectedMenuItemText = ""; 181 | break; 182 | } 183 | 184 | // we have selected the current menu item 185 | if (InputSystem.NewKeys.Contains(Key.Enter)) 186 | { 187 | switch (_selectedMenuItem) 188 | { 189 | case MainMenuOptions.SinglePlayer: 190 | var cs = SceneManager.SceneList.Find(s => s.GetType() == typeof(ChooseSongScene)); 191 | if (cs == null) 192 | { 193 | cs = new ChooseSongScene(_GUIComponents, _centerPolygon, _player, _shaderProgram); 194 | SceneManager.AddScene(cs, this); 195 | } 196 | cs.Visible = true; 197 | break; 198 | case MainMenuOptions.Options: 199 | SceneManager.AddScene(new OptionsScene(), this); 200 | break; 201 | case MainMenuOptions.Exit: 202 | Exit(); 203 | break; 204 | case MainMenuOptions.Update: 205 | SceneManager.AddScene(new UpdateScene(), this); 206 | break; 207 | } 208 | } 209 | } 210 | 211 | public override void Draw(double time) 212 | { 213 | _shaderProgram.Bind(); 214 | _shaderProgram.SetUniform("mvp", SceneManager.ScreenCamera.WorldModelViewProjection); 215 | _shaderProgram.SetUniform("in_color", Color4.White); 216 | 217 | //Draw the player 218 | _player.Draw(time); 219 | 220 | //Draw the center polygon 221 | _centerPolygon.Draw(time); 222 | 223 | _shaderProgram.SetUniform("in_color", Color4.Black); 224 | _centerPolygon.DrawOutline(time); 225 | 226 | //Cleanup the program 227 | _shaderProgram.UnBind(); 228 | 229 | _menuFontDrawing.RefreshBuffers(); 230 | _menuFontDrawing.Draw(); 231 | 232 | SceneManager.DrawTextLine("TURNT NINJA " + _gameVersion, new Vector3(-WindowWidth / 2+5, -WindowHeight / 2 + _versionFont.Font.MaxLineHeight, 0), Color.White, QFontAlignment.Left, _versionFont.Font); 233 | } 234 | 235 | public override void Dispose() 236 | { 237 | // Remove key remapping 238 | InputSystem.KeyRemappings.Remove(Key.KeypadEnter); 239 | 240 | _GUIComponents.Dispose(); 241 | if (_shaderProgram != null) 242 | { 243 | _shaderProgram.Dispose(); 244 | _shaderProgram = null; 245 | } 246 | _menuFontDrawing.Dispose(); 247 | _menuFont.Dispose(); 248 | } 249 | 250 | public override void EnterFocus() 251 | { 252 | } 253 | 254 | public override void ExitFocus() 255 | { 256 | } 257 | } 258 | 259 | enum MainMenuOptions 260 | { 261 | SinglePlayer = 1, 262 | Scores = 2, 263 | Options = 3, 264 | Exit = 4, 265 | Update = 5, 266 | ComingSoon = 0, 267 | None = -1 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/TurntNinja/Generation/StageGeometryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using TurntNinja.Audio; 7 | using TurntNinja.Core; 8 | using TurntNinja.Game; 9 | using ColorMine.ColorSpaces; 10 | using OpenTK.Graphics; 11 | using Substructio.Core.Math; 12 | using Substructio.Graphics.OpenGL; 13 | 14 | namespace TurntNinja.Generation 15 | { 16 | class StageGeometryBuilder 17 | { 18 | private StageGeometry _stageGeometry; 19 | private AudioFeatures _audioFeatures; 20 | private GeometryBuilderOptions _builderOptions; 21 | 22 | private OnsetCollection _onsets; 23 | private OnsetDrawing _onsetDrawing; 24 | private float[] _beatFrequencies; 25 | private Color4 _segmentStartColour; 26 | private Random _random; 27 | 28 | private List _goodBeats; 29 | 30 | public StageGeometry Build(AudioFeatures audioFeatures, Random random, GeometryBuilderOptions builderOptions) 31 | { 32 | _audioFeatures = audioFeatures; 33 | _builderOptions = builderOptions; 34 | _random = random; 35 | _builderOptions.RandomFunction = _random; 36 | 37 | BuildGoodBeatsList(); 38 | BuildBeatFrequencyList(); 39 | 40 | BuildGeometry(); 41 | SetStartColour(); 42 | 43 | var backgroundPolygon = new PolarPolygon(6, new PolarVector(0.5, 0), 500000, -20, 0); 44 | backgroundPolygon.ShaderProgram = _builderOptions.GeometryShaderProgram; 45 | 46 | return new StageGeometry(_onsets, _onsetDrawing, _segmentStartColour, _random) {BackgroundPolygon = backgroundPolygon}; 47 | } 48 | 49 | private void BuildGoodBeatsList() 50 | { 51 | //sort onset list by time 52 | var sorted = _audioFeatures.OnsetTimes.OrderBy(f => f); 53 | _goodBeats = new List(); 54 | float prevTime = -1.0f; 55 | 56 | //filter out beats that are too close 57 | foreach (var b in sorted) 58 | { 59 | if (b - prevTime < _builderOptions.BeatSkipDistance) 60 | continue; 61 | _goodBeats.Add(b); 62 | prevTime = b; 63 | } 64 | } 65 | 66 | private void BuildBeatFrequencyList() 67 | { 68 | _beatFrequencies = new float[_goodBeats.Count]; 69 | int lookAhead = 5; 70 | int halfFrequencySampleSize = 4; 71 | int forwardWeighting = 1; 72 | 73 | for (int i = 0; i < _beatFrequencies.Length; i++) 74 | { 75 | int weight = 0; 76 | float differenceSum = 0; 77 | int total = 0; 78 | //for (int j = i - halfFrequencySampleSize < 1 ? 1 : i-halfFrequencySampleSize; j <= i; j++) 79 | //{ 80 | // weight++; 81 | // differenceSum += (sorted[j] - sorted[j-1]); 82 | // total += 1; 83 | //} 84 | 85 | //weight = halfFrequencySampleSize + forwardWeighting; 86 | int count = i + halfFrequencySampleSize + 1> _beatFrequencies.Length - 1 ? _beatFrequencies.Length - 1 : i + halfFrequencySampleSize + 1; 87 | for (int j = i+1; j <= count; j++) 88 | { 89 | differenceSum += (_goodBeats[j] - _goodBeats[j-1]); 90 | total += 1; 91 | //weight--; 92 | } 93 | 94 | _beatFrequencies[i] = 1 / (differenceSum / total); 95 | } 96 | 97 | _beatFrequencies[_beatFrequencies.Length - 1] = _beatFrequencies[_beatFrequencies.Length - 2]; 98 | 99 | //_beatFrequencies = _audioFeatures.Onsets.Select(o => o.OnsetAmplitude).ToArray(); 100 | } 101 | 102 | private void BuildGeometry() 103 | { 104 | _onsets = new OnsetCollection(_goodBeats.Count); 105 | _onsets.AddOnsets(_goodBeats.ToArray(), _beatFrequencies); 106 | _onsetDrawing = new OnsetDrawing(_onsets, _builderOptions.GeometryShaderProgram); 107 | 108 | //intialise state variables for algorithim 109 | int prevStart = 0; 110 | int prevSkip = 0; 111 | //set initial previous time to -1 so that the first polygon generated is always unique and doesn't trigger 'beat too close to previous' case 112 | double prevTime = -1.0; 113 | float samePatternChance = 0.45f; 114 | float veryCloseJoinChance = 0.45f; 115 | float joinFunctionMultiplier = 10.0f / 1.5f; 116 | 117 | bool[] sides; 118 | 119 | var structureList = new List>(); 120 | 121 | List structures = new List(); 122 | 123 | int currentStructureIndex = -1; 124 | //first pass to look for structures 125 | foreach (var b in _goodBeats) 126 | { 127 | // are we extending an existing structure? 128 | double t = Math.Exp(-joinFunctionMultiplier*(b - prevTime - _builderOptions.VeryCloseDistance) + Math.Log(veryCloseJoinChance)); 129 | if (_random.NextDouble() < t) 130 | { 131 | structures[currentStructureIndex].End = b; 132 | } 133 | 134 | // else create new structure 135 | else 136 | { 137 | structures.Add(new OnsetStructure { Start = b, End = b }); 138 | currentStructureIndex = structures.Count - 1; 139 | } 140 | 141 | // update previous onset time 142 | prevTime = b; 143 | } 144 | 145 | prevTime = -1; 146 | 147 | //traverse sorted onset list and generate geometry for each onset 148 | foreach (var s in structures) 149 | { 150 | int start; 151 | 152 | //generate the skip pattern. Highest probablility is of obtaining a 1 skip pattern - no sides are skipped at all. 153 | int skip = _builderOptions.SkipFunction(); 154 | if (s.Start - prevTime < _builderOptions.VeryCloseDistance) 155 | { 156 | //this beat is very close to the previous one, use the same start orientation and skip pattern 157 | start = prevStart; 158 | skip = prevSkip; 159 | } 160 | else if (s.Start - prevTime < _builderOptions.CloseDistance) 161 | { 162 | if (_random.NextDouble() < 0.5) 163 | { 164 | //randomly choose relative orientation difference compared to previous beat 165 | var r = _random.Next(0, 2); 166 | if (r == 0) r = -1; 167 | 168 | //this beat is reasonably close to the previous one, use the same skip pattern but a different (+/- 1) orientation 169 | start = (prevStart + 6) + r; 170 | if (_random.NextDouble() < samePatternChance) 171 | skip = prevSkip; 172 | } 173 | else 174 | { 175 | start = prevStart; 176 | } 177 | } 178 | else 179 | { 180 | //choose a random start position for this polygon 181 | start = _random.Next(_builderOptions.MaxSides - 1); 182 | while (start == prevStart && _random.NextDouble() > 0.15) 183 | start = _random.Next(_builderOptions.MaxSides - 1); 184 | } 185 | 186 | sides = new bool[6]; 187 | for (int i = 0; i < 6; i++) 188 | { 189 | //ensure that if skip is set to 1, we still leave an opening 190 | if (skip == 1 && i == start % 6) sides[i] = false; 191 | //if skip is not set to 1 and this is not a side we are skipping, enable this side 192 | else if ((i + start) % skip == 0) sides[i] = true; 193 | //else disable sides by default 194 | else sides[i] = false; 195 | } 196 | 197 | _onsetDrawing.AddOnsetDrawing(sides.ToList(), _builderOptions.PolygonVelocity, _builderOptions.PolygonWidth + (s.End - s.Start) * _builderOptions.PolygonVelocity.Radius, _builderOptions.PolygonMinimumRadius, s.Start); 198 | 199 | //update the variables holding the previous state of the algorithim. 200 | prevTime = s.End; 201 | prevStart = start; 202 | prevSkip = skip; 203 | } 204 | _onsetDrawing.Initialise(); 205 | _onsets.Initialise(); 206 | } 207 | 208 | private void SetStartColour() 209 | { 210 | //initialise algorithim values 211 | double maxStep = (double)360 / (20); 212 | double minStep = _builderOptions.MinimumColourStepMultiplier * maxStep; 213 | double startAngle = _random.NextDouble() * 360; 214 | double prevAngle = startAngle - maxStep; 215 | 216 | var step = _random.NextDouble() * (maxStep - minStep) + minStep; 217 | double angle = prevAngle; 218 | angle = MathUtilities.Normalise(step + angle, 0, 360); 219 | var rgb = HUSL.ColorConverter.HUSLToRGB(new List{angle, _builderOptions.Saturation, _builderOptions.Lightness}); 220 | 221 | prevAngle = angle; 222 | 223 | _segmentStartColour = new Color4((byte)((rgb[0])*255), (byte)((rgb[1])*255), (byte)((rgb[2])*255), 255); 224 | } 225 | } 226 | 227 | class OnsetStructure 228 | { 229 | public double Start; 230 | public double End; 231 | } 232 | 233 | class GeometryBuilderOptions 234 | { 235 | public int MaxSides = 6; 236 | 237 | public PolarVector PolygonVelocity = new PolarVector(0, 600); 238 | public float PolygonWidth = 40f; 239 | public float PolygonMinimumRadius = 130f; 240 | 241 | public float VeryCloseDistance = 0.2f; 242 | public float CloseDistance = 0.4f; 243 | public float BeatSkipDistance = 0.0f; 244 | 245 | public Random RandomFunction; 246 | 247 | /// 248 | /// Method to use to get skip value. 249 | /// Default returns value between 1 and 3 inclusive 250 | /// 251 | /// Skip value 252 | public delegate int SkipDistributionFunction(); 253 | public SkipDistributionFunction SkipFunction; 254 | 255 | public int Saturation = 50; 256 | public int Lightness = 30; 257 | public double MinimumColourStepMultiplier = 0.25; 258 | 259 | public ShaderProgram GeometryShaderProgram; 260 | 261 | public GeometryBuilderOptions(ShaderProgram geometryShaderProgram) 262 | { 263 | GeometryShaderProgram = geometryShaderProgram; 264 | SkipFunction = () => (int) (3*Math.Pow(RandomFunction.NextDouble(), 2)) + 1; 265 | } 266 | 267 | public void ApplyDifficulty(DifficultyOptions options) 268 | { 269 | PolygonVelocity.Radius = options.Speed; 270 | VeryCloseDistance = options.VeryCloseDistance; 271 | CloseDistance = options.CloseDistance; 272 | BeatSkipDistance = options.BeatSkipDistance; 273 | } 274 | } 275 | } 276 | --------------------------------------------------------------------------------