rsgt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/README.markdown +139 -0
- data/Rakefile +10 -0
- data/bin/rsgt +5 -0
- data/lib/rsgt.rb +42 -0
- data/lib/rsgt/audio_extractor.rb +25 -0
- data/lib/rsgt/cli.rb +89 -0
- data/lib/rsgt/command_runner.rb +41 -0
- data/lib/rsgt/encrypted_steam_file.rb +44 -0
- data/lib/rsgt/multipacker.rb +114 -0
- data/lib/rsgt/multipacker/config_processor.rb +44 -0
- data/lib/rsgt/repacker.rb +37 -0
- data/lib/rsgt/rs_custom_song_toolkit.rb +41 -0
- data/lib/rsgt/saved_game.rb +37 -0
- data/lib/rsgt/saved_game/learn_a_song/song.rb +46 -0
- data/lib/rsgt/saved_game/score_attack/song.rb +38 -0
- data/lib/rsgt/saved_game/song_collection.rb +16 -0
- data/lib/rsgt/saved_game/statistics.rb +91 -0
- data/lib/rsgt/saved_game/statistics/song.rb +58 -0
- data/lib/rsgt/steam.rb +45 -0
- data/lib/rsgt/unpacked_psarc.rb +72 -0
- data/lib/rsgt/version.rb +3 -0
- data/lib/rsgt/vocals_extractor.rb +24 -0
- data/lib/rsgt/wav_shifter.rb +54 -0
- data/lib/rsgt/wwise_converter.rb +99 -0
- data/rsgt.gemspec +31 -0
- data/spec/lib/rsgt/audio_extractor_spec.rb +14 -0
- data/spec/lib/rsgt/command_runner_spec.rb +37 -0
- data/spec/lib/rsgt/repacker_spec.rb +24 -0
- data/spec/lib/rsgt/rs_custom_song_toolkit_spec.rb +47 -0
- data/spec/lib/rsgt/saved_game/learn_a_song/song_spec.rb +15 -0
- data/spec/lib/rsgt/saved_game_spec.rb +49 -0
- data/spec/lib/rsgt/unpacked_psarc_spec.rb +35 -0
- data/spec/lib/rsgt/vocals_extractor_spec.rb +14 -0
- data/spec/lib/rsgt/wav_shifter_spec.rb +30 -0
- data/spec/lib/rsgt/wwise_converter_spec.rb +22 -0
- data/spec/spec_helper.rb +11 -0
- metadata +168 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module RSGuitarTech
|
2
|
+
class UnpackedPSARC
|
3
|
+
|
4
|
+
def self.from_psarc(psarc, opts)
|
5
|
+
raise ArgumentError, "A block is required" unless block_given?
|
6
|
+
raise ArgumentError, "#{psarc} not found!" unless File.exist?(psarc)
|
7
|
+
|
8
|
+
puts "Unpacking psarc to temporary location..."
|
9
|
+
# Use pyrocksmith to unpack the file, and check that it worked..
|
10
|
+
CommandRunner.run! ["pyrocksmith", "--unpack", psarc, "--no-crypto"]
|
11
|
+
expected_extraction = File.basename psarc, ".*"
|
12
|
+
raise ArgumentError unless File.exist?(expected_extraction + "/appid.appid")
|
13
|
+
|
14
|
+
# Move it to a tmpdir, and yield
|
15
|
+
Dir.mktmpdir do |dir|
|
16
|
+
FileUtils.mv expected_extraction, dir
|
17
|
+
yield self.new(psarc, dir, opts)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :psarc, :tmpdir, :opts
|
22
|
+
|
23
|
+
def initialize(psarc, tmpdir, opts)
|
24
|
+
@psarc = psarc
|
25
|
+
@tmpdir = tmpdir
|
26
|
+
@opts = opts
|
27
|
+
end
|
28
|
+
|
29
|
+
def repack!
|
30
|
+
puts "Repacking altered psarc..."
|
31
|
+
CommandRunner.run! ["pyrocksmith", "--no-crypto", "--pack", "#{tmpdir}/#{expected_extraction}"]
|
32
|
+
puts "Done! ✅"
|
33
|
+
end
|
34
|
+
|
35
|
+
def expected_extraction
|
36
|
+
File.basename psarc, ".*"
|
37
|
+
end
|
38
|
+
|
39
|
+
def manifest(type)
|
40
|
+
Dir.glob("#{tmpdir}/#{expected_extraction}/manifests/*/*.json").detect { |filename| filename.include? type.to_s }
|
41
|
+
end
|
42
|
+
|
43
|
+
def sng_bin(type)
|
44
|
+
Dir.glob("#{tmpdir}/#{expected_extraction}/songs/bin/macos/*.sng").detect { |filename| filename.include? type.to_s }
|
45
|
+
end
|
46
|
+
|
47
|
+
def sng_xml
|
48
|
+
Dir.glob("#{tmpdir}/#{expected_extraction}/songs/bin/macos/*.xml").first
|
49
|
+
end
|
50
|
+
|
51
|
+
def audio_track(track: :main)
|
52
|
+
audio_tracks[track]
|
53
|
+
end
|
54
|
+
|
55
|
+
def ogg_track
|
56
|
+
audio_tracks[:main].gsub ".wem", ".ogg"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def audio_tracks
|
62
|
+
@audio_tracks ||= begin
|
63
|
+
both_wems = Dir.glob("#{tmpdir}/#{expected_extraction}/audio/mac/*.wem").sort_by { |wem| File.stat(wem).size }
|
64
|
+
|
65
|
+
{
|
66
|
+
main: both_wems.last,
|
67
|
+
preview: both_wems.first
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/rsgt/version.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module RSGuitarTech
|
2
|
+
class VocalsExtractor
|
3
|
+
|
4
|
+
attr_accessor :psarc, :opts, :unpacked
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
@psarc = File.expand_path(opts.delete :psarc)
|
8
|
+
@opts = opts
|
9
|
+
end
|
10
|
+
|
11
|
+
def extract!
|
12
|
+
puts ".. Extracting vocals from sng to xml..."
|
13
|
+
UnpackedPSARC.from_psarc(psarc, opts) do |unpacked|
|
14
|
+
CommandRunner.run! RSCustomSongToolkit.sng2xml(
|
15
|
+
manifest: unpacked.manifest(:vocals),
|
16
|
+
input: unpacked.sng_bin(:vocals)
|
17
|
+
)
|
18
|
+
raise StandardError, "Failed to extract vocals" unless File.exist? unpacked.sng_xml
|
19
|
+
|
20
|
+
FileUtils.cp unpacked.sng_xml, "."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RSGuitarTech
|
2
|
+
class WavShifter
|
3
|
+
|
4
|
+
attr_accessor :wav, :dir, :amount
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
@wav = File.expand_path(opts.delete :wav)
|
8
|
+
@dir = opts[:dir]
|
9
|
+
@amount = opts[:amount].to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def shift!
|
13
|
+
case dir
|
14
|
+
when "forward" then forward!
|
15
|
+
when /back/ then backward!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def forward!
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def backward!
|
26
|
+
Dir.mktmpdir do |tmpdir|
|
27
|
+
puts "Trimming #{amount}ms from beginning..."
|
28
|
+
status = CommandRunner.run! [
|
29
|
+
"ffmpeg", "-i", wav,
|
30
|
+
"-ss", start_trim_timestamp,
|
31
|
+
"-acodec", "copy",
|
32
|
+
"#{tmpdir}/shifted.wav"
|
33
|
+
]
|
34
|
+
if status.success?
|
35
|
+
puts "Success!"
|
36
|
+
FileUtils.cp "#{tmpdir}/shifted.wav", wav
|
37
|
+
else
|
38
|
+
puts "Failed!"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
TS_CONV = [1, 1000, 60000, 3600000]
|
44
|
+
|
45
|
+
def start_trim_timestamp
|
46
|
+
h = (amount / TS_CONV[3])
|
47
|
+
m = (amount % TS_CONV[3] / TS_CONV[2])
|
48
|
+
s = (amount % TS_CONV[2] / TS_CONV[1])
|
49
|
+
ms = (amount % TS_CONV[1])
|
50
|
+
|
51
|
+
"%02d:%02d:%02d.%03d" % [h, m, s, ms]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module RSGuitarTech
|
2
|
+
class WWiseConverter
|
3
|
+
|
4
|
+
TEMPLATE_TAR_BZ = "/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/WwiseYEAR.tar.bz2"
|
5
|
+
|
6
|
+
attr_accessor :source_file, :wwise_version, :quality, :chorus, :dir
|
7
|
+
|
8
|
+
def initialize(source_file, opts = {})
|
9
|
+
@source_file = source_file
|
10
|
+
@wwise_version = opts[:wwise_ver]
|
11
|
+
@quality = opts[:quality]
|
12
|
+
@chorus = opts[:chorus]
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert!
|
16
|
+
Dir.mktmpdir do |dir|
|
17
|
+
@dir = dir
|
18
|
+
|
19
|
+
extract_base_template
|
20
|
+
prepare_template
|
21
|
+
copy_source
|
22
|
+
ffmpeg_preview
|
23
|
+
run_wwise
|
24
|
+
if wwise_version[0..3].to_i >= 2016
|
25
|
+
downgrade_headers "#{template_dir}/GeneratedSoundBanks/Windows/144151451.wem"
|
26
|
+
downgrade_headers "#{template_dir}/GeneratedSoundBanks/Windows/309608343.wem"
|
27
|
+
end
|
28
|
+
yield self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def extract_base_template
|
33
|
+
just_tar = template_tar_bz.gsub(".bz2", "")
|
34
|
+
CommandRunner.run! ["bunzip2", "-k", template_tar_bz] # Unzip Wwise2016.tar.bz2 to Wwise2016.tar
|
35
|
+
CommandRunner.run! ["tar", "-xzvf", just_tar, "-C", dir] # Untar Wwise2016.tar to the workdir
|
36
|
+
end
|
37
|
+
|
38
|
+
def prepare_template
|
39
|
+
CommandRunner.run! ["mkdir", "-p", "#{template_dir}/.cache/Windows/SFX"]
|
40
|
+
CommandRunner.run! ["sed", "-i.bak", "'s/%QF1%/#{quality}/'", default_work_unit.shellescape], skip_escaping: true
|
41
|
+
CommandRunner.run! ["sed", "-i.bak", "'s/%QF2%/#{quality}/'", default_work_unit.shellescape], skip_escaping: true
|
42
|
+
end
|
43
|
+
|
44
|
+
def copy_source
|
45
|
+
FileUtils.cp source_file, "#{template_dir}/Originals/SFX/Audio.wav"
|
46
|
+
end
|
47
|
+
|
48
|
+
def ffmpeg_preview
|
49
|
+
CommandRunner.run! ["ffmpeg", "-i", source_file, "-ss", chorus, "-t", "30", "#{template_dir}/Originals/SFX/Audio_preview.wav"]
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_wwise
|
53
|
+
wwise_cmd = [
|
54
|
+
wwise_cli,
|
55
|
+
"#{template_dir}/Template.wproj",
|
56
|
+
"-GenerateSoundBanks",
|
57
|
+
"-Platform",
|
58
|
+
"Windows",
|
59
|
+
"-NoWwiseDat",
|
60
|
+
"-ClearAudioFileCache",
|
61
|
+
"-Save"
|
62
|
+
]
|
63
|
+
wwise_cmd << "-Verbose" if RSGuitarTech.verbose
|
64
|
+
CommandRunner.run! wwise_cmd
|
65
|
+
end
|
66
|
+
|
67
|
+
def downgrade_headers(file)
|
68
|
+
CommandRunner.run! %Q{printf "$(printf '\\x%02X' 03)" | dd of=#{file} bs=1 seek=40 count=1 conv=notrunc}, skip_escaping: true
|
69
|
+
CommandRunner.run! %Q{printf "$(printf '\\x%02X' 00)" | dd of=#{file} bs=1 seek=41 count=1 conv=notrunc}, skip_escaping: true
|
70
|
+
CommandRunner.run! %Q{printf "$(printf '\\x%02X' 00)" | dd of=#{file} bs=1 seek=42 count=1 conv=notrunc}, skip_escaping: true
|
71
|
+
CommandRunner.run! %Q{printf "$(printf '\\x%02X' 00)" | dd of=#{file} bs=1 seek=43 count=1 conv=notrunc}, skip_escaping: true
|
72
|
+
end
|
73
|
+
|
74
|
+
def results
|
75
|
+
{
|
76
|
+
main: "#{template_dir}/GeneratedSoundBanks/Windows/144151451.wem",
|
77
|
+
preview: "#{template_dir}/GeneratedSoundBanks/Windows/309608343.wem"
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def template_dir
|
84
|
+
"#{dir}/Template"
|
85
|
+
end
|
86
|
+
|
87
|
+
def template_tar_bz
|
88
|
+
TEMPLATE_TAR_BZ.dup.gsub "YEAR", wwise_version[0..3]
|
89
|
+
end
|
90
|
+
|
91
|
+
def wwise_cli
|
92
|
+
"/Applications/Audiokinetic/Wwise #{wwise_version}/Wwise.app/Contents/Tools/WwiseCLI.sh"
|
93
|
+
end
|
94
|
+
|
95
|
+
def default_work_unit
|
96
|
+
"#{template_dir}/Interactive Music Hierarchy/Default Work Unit.wwu"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/rsgt.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "rsgt/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.name = "rsgt"
|
8
|
+
s.version = RSGuitarTech::VERSION
|
9
|
+
s.authors = ["Justin Aiken"]
|
10
|
+
s.email = ["60tonangel@gmail.com"]
|
11
|
+
s.license = "MIT"
|
12
|
+
s.homepage = "https://github.com/JustinAiken/rsgt"
|
13
|
+
s.summary = %q{Rocksmith}
|
14
|
+
s.description = %q{Rocksmith}
|
15
|
+
|
16
|
+
s.rubyforge_project = "rsgt"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.bindir = "bin"
|
22
|
+
s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
23
|
+
|
24
|
+
s.add_dependency "bindata"
|
25
|
+
s.add_dependency "trollop", "~> 2.1"
|
26
|
+
s.add_dependency "rainbow", "~> 2.2"
|
27
|
+
s.add_dependency "activesupport", ">= 3.0"
|
28
|
+
|
29
|
+
s.add_development_dependency "rspec", "~> 3.5"
|
30
|
+
s.add_development_dependency "pry"
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RSGuitarTech::AudioExtractor do
|
4
|
+
let(:extractor) { described_class.new psarc: psarc }
|
5
|
+
let(:psarc) { "foo_m.psarc" }
|
6
|
+
|
7
|
+
describe "#extract!" do
|
8
|
+
it "extracts" do
|
9
|
+
allow(extractor).to receive :puts
|
10
|
+
expect(RSGuitarTech::UnpackedPSARC).to receive :from_psarc
|
11
|
+
extractor.extract!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RSGuitarTech::CommandRunner do
|
4
|
+
let(:runner) { described_class }
|
5
|
+
let(:args) { "ls" }
|
6
|
+
let(:skip_escaping) { false }
|
7
|
+
subject(:run!) { runner.run! args, skip_escaping: skip_escaping }
|
8
|
+
|
9
|
+
it { should be_success }
|
10
|
+
|
11
|
+
context "with spaces" do
|
12
|
+
let(:args) { "ls -al" }
|
13
|
+
|
14
|
+
after { run! }
|
15
|
+
|
16
|
+
it "escapes the command" do
|
17
|
+
expect(Open3).to receive(:capture3).with "ls\\ -al"
|
18
|
+
end
|
19
|
+
|
20
|
+
context "but skip_escaping: true" do
|
21
|
+
let(:skip_escaping) { true }
|
22
|
+
|
23
|
+
it "runs the command as-is" do
|
24
|
+
expect(Open3).to receive(:capture3).with "ls -al"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "verbosely" do
|
30
|
+
before { allow(RSGuitarTech).to receive(:verbose).and_return true }
|
31
|
+
|
32
|
+
it "does some puts" do
|
33
|
+
expect_any_instance_of(described_class).to receive(:puts).at_least :once
|
34
|
+
run!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RSGuitarTech::Repacker do
|
4
|
+
let(:repacker) { described_class.new psarc: "foo_m.psarc", vocals_xml: vocals_xml, audio: audio }
|
5
|
+
let(:vocals_xml) { "foo_vocals.xml" }
|
6
|
+
let(:audio) { "audio.wav" }
|
7
|
+
|
8
|
+
let(:unpacked) { double RSGuitarTech::UnpackedPSARC, manifest: "manifest.json", audio_track: "track.wem" }
|
9
|
+
let(:converter) { double RSGuitarTech::WWiseConverter, results: {main: "main.wav"} }
|
10
|
+
|
11
|
+
describe "#repack!" do
|
12
|
+
before { allow(repacker).to receive :puts }
|
13
|
+
|
14
|
+
it "repacks" do
|
15
|
+
expect(RSGuitarTech::UnpackedPSARC).to receive(:from_psarc).and_yield unpacked
|
16
|
+
expect_any_instance_of(RSGuitarTech::WWiseConverter).to receive(:convert!).and_yield converter
|
17
|
+
expect(FileUtils).to receive(:cp).with "main.wav", "track.wem"
|
18
|
+
expect(RSGuitarTech::CommandRunner).to receive :run!
|
19
|
+
expect(unpacked).to receive :repack!
|
20
|
+
|
21
|
+
repacker.repack!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RSGuitarTech::RSCustomSongToolkit do
|
4
|
+
let(:rs_custom) { described_class }
|
5
|
+
|
6
|
+
it ".ww2ogg" do
|
7
|
+
expect(rs_custom.ww2ogg "foo.wem").to eq [
|
8
|
+
"wine",
|
9
|
+
"/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/ww2ogg.exe",
|
10
|
+
"foo.wem",
|
11
|
+
"--pcb",
|
12
|
+
described_class::PCB_PATH
|
13
|
+
]
|
14
|
+
end
|
15
|
+
|
16
|
+
it ".revorb" do
|
17
|
+
expect(rs_custom.revorb "foo.ogg").to eq [
|
18
|
+
"wine",
|
19
|
+
"/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/revorb.exe",
|
20
|
+
"foo.ogg"
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
it ".sng2xml" do
|
25
|
+
expect(rs_custom.sng2xml manifest: "manifest.json", input: "foo.sng").to eq [
|
26
|
+
"wine",
|
27
|
+
"/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/sng2014.exe",
|
28
|
+
"--sng2xml",
|
29
|
+
"--manifest=manifest.json",
|
30
|
+
"--input=foo.sng",
|
31
|
+
"--arrangement=Vocal",
|
32
|
+
"--platform=Mac"
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
it ".xml2sng" do
|
37
|
+
expect(rs_custom.xml2sng manifest: "manifest.json", input: "foo.xml").to eq [
|
38
|
+
"wine",
|
39
|
+
"/Applications/RocksmithCustomSongToolkit.app/Contents/Resources/sng2014.exe",
|
40
|
+
"--xml2sng",
|
41
|
+
"--manifest=manifest.json",
|
42
|
+
"--input=foo.xml",
|
43
|
+
"--arrangement=Vocal",
|
44
|
+
"--platform=Mac"
|
45
|
+
]
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RSGuitarTech::SavedGame::LearnASong::Song do
|
4
|
+
pending ".from"
|
5
|
+
|
6
|
+
describe "#==" do
|
7
|
+
subject { described_class.new id: "123", timestamp: nil, dynamic_difficulty: nil, play_next_stats: nil }
|
8
|
+
let(:song_b) { described_class.new id: "123", timestamp: nil, dynamic_difficulty: nil, play_next_stats: nil }
|
9
|
+
let(:song_c) { described_class.new id: "456", timestamp: nil, dynamic_difficulty: nil, play_next_stats: nil }
|
10
|
+
|
11
|
+
it { should eq song_b }
|
12
|
+
it { should_not eq song_c }
|
13
|
+
it { should_not eq nil }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe RSGuitarTech::SavedGame do
|
4
|
+
let(:saved_game) { described_class.new json, :filename }
|
5
|
+
|
6
|
+
pending ".from"
|
7
|
+
|
8
|
+
describe "#options" do
|
9
|
+
subject { saved_game.options }
|
10
|
+
let(:json) { {"Options" => {"foo" => "bar"}} }
|
11
|
+
it { should eq "foo" => "bar" }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#statistics" do
|
15
|
+
subject { saved_game.statistics }
|
16
|
+
let(:json) { {"Stats" => {"foo" => "bar"}} }
|
17
|
+
it { should be_a RSGuitarTech::SavedGame::Statistics }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#song_statistics" do
|
21
|
+
subject(:song_statistics) { saved_game.song_statistics }
|
22
|
+
let(:json) { {"Stats" => {"Songs" => {"abc" => "songdata"}}} }
|
23
|
+
it { should be_a RSGuitarTech::SavedGame::SongCollection }
|
24
|
+
|
25
|
+
it "is a keyed collection of Statistics::Song's" do
|
26
|
+
expect(song_statistics["abc"]).to be_a RSGuitarTech::SavedGame::Statistics::Song
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#score_attack" do
|
31
|
+
subject(:score_attack) { saved_game.score_attack }
|
32
|
+
let(:json) { {"SongsSA" => {"abc" => "songdata"}} }
|
33
|
+
it { should be_a RSGuitarTech::SavedGame::SongCollection }
|
34
|
+
|
35
|
+
it "is a keyed collection of Statistics::Song's" do
|
36
|
+
expect(score_attack["abc"]).to be_a RSGuitarTech::SavedGame::ScoreAttack::Song
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#learn_a_song" do
|
41
|
+
subject(:learn_a_song) { saved_game.learn_a_song }
|
42
|
+
let(:json) { {"Songs" => {"abc" => "songdata"}} }
|
43
|
+
it { should be_a RSGuitarTech::SavedGame::SongCollection }
|
44
|
+
|
45
|
+
it "is a keyed collection of Statistics::Song's" do
|
46
|
+
expect(learn_a_song["abc"]).to be_a RSGuitarTech::SavedGame::LearnASong::Song
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|