live_set 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +71 -29
- data/als_notes.md +58 -0
- data/exe/als_delta +8 -0
- data/exe/live_set +1 -8
- data/lib/als_delta/als_delta_class.rb +60 -0
- data/lib/als_delta/als_delta_options.rb +58 -0
- data/lib/common/common.rb +17 -0
- data/lib/common/run.rb +21 -0
- data/lib/common/util.rb +39 -0
- data/lib/live_set/live_set_all_tracks.rb +34 -0
- data/lib/live_set/live_set_audio_clip.rb +26 -0
- data/lib/live_set/live_set_audio_track.rb +34 -0
- data/lib/{llive_set_class.rb → live_set/live_set_class.rb} +11 -38
- data/lib/live_set/live_set_class_private.rb +29 -0
- data/lib/{options.rb → live_set/live_set_options.rb} +9 -8
- data/lib/live_set/live_set_scene.rb +9 -0
- data/lib/live_set/version.rb +1 -1
- data/lib/live_set.rb +40 -19
- data/live_set.gemspec +3 -1
- metadata +45 -6
- data/lib/common.rb +0 -18
- data/lib/util.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9caef58c0bed30ead696f9d64848c74038aa520d83a2c47796cb0081c0ad6b14
|
4
|
+
data.tar.gz: 85906715baa37c0ed5a2fd4f0a101e37d5c5728a4385f0754d120514b18d7b89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88404bf0b8d04663da0d7a334a8affd9d930df965af1b3fed1b3a2b9286881380ecba7882badbf71d0612674a3f77775a436a6e5a505a4bb9b25801522ad5ab6
|
7
|
+
data.tar.gz: d633040d514b77e4984e936c4676fd5f124ee30da405b6c612f916edbd33625cbacb0a1d35812aaae393f03436cab2054144e54a75db1214ba64196c92511cf4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
|
4
|
+
## 0.2.1
|
5
|
+
|
6
|
+
* Corrected missing help method for the `live_set` command.
|
7
|
+
* Prompts user if they want the extra directory renamed to `Ableton Project Info-`.
|
8
|
+
|
9
|
+
|
10
|
+
## 0.2.0
|
11
|
+
|
12
|
+
* Checks `Ableton Project Info` validity all up the directory tree.
|
13
|
+
* Reports the clips in the set, and their sizes.
|
14
|
+
* Generates a warning if the total size of the clips might cause freezing to fail.
|
15
|
+
|
16
|
+
|
3
17
|
## 0.1.0
|
4
18
|
|
5
19
|
* Initial release.
|
data/README.md
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# `Live_set` [![Gem Version](https://badge.fury.io/rb/live_set.svg)](https://badge.fury.io/rb/live_set)
|
2
2
|
|
3
|
-
This program can
|
4
|
-
|
3
|
+
This program can:
|
4
|
+
|
5
|
+
1. Report the version compatibility of an Ableton Live set.
|
6
|
+
2. Display the location and size of the samples in each track.
|
7
|
+
3. Shows frozen tracks.
|
8
|
+
4. Make a copy of an Ableton Live 12 set and save to Live 11 format.
|
9
|
+
5. Detect environmental problems, for example unwanted directories called `Ableton Project Info` in parent directories.
|
10
|
+
6. Detect sets that are too large to be frozen, which means they are too large to transfer to Push 3 Standalone.
|
11
|
+
7. Detect tracks that are not frozen, which means they are not ready to be transferred to Push 3 Standalone.
|
5
12
|
|
6
13
|
This program has successfully converted the Ableton Live 12 demo project to Live 11.
|
7
14
|
|
@@ -14,19 +21,24 @@ Simply type:
|
|
14
21
|
$ gem install live_set
|
15
22
|
```
|
16
23
|
|
24
|
+
If the above does not work because you need to install Ruby, please
|
25
|
+
[follow these instructions](https://www.mslinn.com/ruby/1000-ruby-setup.html).
|
26
|
+
|
17
27
|
|
18
28
|
## Usage
|
19
29
|
|
20
|
-
```
|
30
|
+
```text
|
21
31
|
$ live_set
|
22
|
-
Displays information about a Live set or converts a Live 12 set
|
32
|
+
Displays information about a Live set or converts a copy of a Live 12 set in Live 11 format.
|
33
|
+
Also verifies that 'Ableton Project Info' is in the same directory as the .als file,
|
34
|
+
and there is no parent directory with a directory of that name.
|
23
35
|
|
24
36
|
Syntax: live_set OPTIONS PATH_TO_ALS_FILE
|
25
37
|
|
26
38
|
Environment variables used in PATH_TO_ALS_FILE are expanded.
|
27
39
|
|
28
40
|
Options are:
|
29
|
-
-11 Convert the Live 12 set to Live 11 format.
|
41
|
+
-11 Convert a copy of the Live 12 set to Live 11 format.
|
30
42
|
-f Overwrite any existing Live 11 set.
|
31
43
|
-h Display this help message.
|
32
44
|
|
@@ -36,24 +48,53 @@ Example:
|
|
36
48
|
|
37
49
|
### Report
|
38
50
|
|
39
|
-
Report the version of the Ableton Live
|
40
|
-
|
41
|
-
```
|
42
|
-
$ live_set $
|
43
|
-
/mnt/
|
44
|
-
Created by Live
|
45
|
-
Major version
|
46
|
-
|
47
|
-
SchemaChangeCount
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
Report the version of the [Ableton Live 12 Demo Project](https://help.ableton.com/hc/en-us/articles/209774005-Location-of-the-default-Demo-Set):
|
52
|
+
|
53
|
+
```text
|
54
|
+
$ live_set "$ableton_media/Projects/Ableton Live 12 Demo Project/Ableton Live 12 Demo.als"
|
55
|
+
/mnt/e/media/Ableton/Projects/Ableton Live 12 Demo Project/Ableton Live 12 Demo.als
|
56
|
+
Created by Ableton Live 12.0
|
57
|
+
Major version 5
|
58
|
+
Minor version v12.0_12049
|
59
|
+
SchemaChangeCount 7
|
60
|
+
Revision 5094b92fa547974769f44cf233f1474777d9434a
|
61
|
+
Total set size: 312.5 MB
|
62
|
+
Warning: some tracks are not frozen, so this set should not be transferred to Push 3 Standalone.
|
63
|
+
4 tracks:
|
64
|
+
Track 'Vocal Main' (12 clips, totaling 154.0 MB)
|
65
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
66
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
67
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
68
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
69
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
70
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
71
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
72
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
73
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
74
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
75
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
76
|
+
Samples/Imported/Vocal Patience Main.wav 12.8 MB 2024-01-31 22:00:34
|
77
|
+
|
78
|
+
Track 'Vocal Harmony' (12 clips, totaling 154.0 MB)
|
79
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
80
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
81
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
82
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
83
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
84
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
85
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
86
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
87
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
88
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
89
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
90
|
+
Samples/Imported/Vocal Patience Harmony.wav 12.8 MB 2024-01-31 22:00:04
|
91
|
+
|
92
|
+
Track 'Vocal Hum' (1 clips, totaling 2.9 MB)
|
93
|
+
Samples/Imported/Vocal Patience Hum.wav 2.9 MB 2024-01-31 22:03:22
|
94
|
+
|
95
|
+
Track 'Vocal Adlib' (2 clips, totaling 1.7 MB)
|
96
|
+
Samples/Imported/Vocal Patience Adlib.wav 827.9 kB 2024-01-31 22:00:22
|
97
|
+
Samples/Imported/Vocal Patience Adlib.wav 827.9 kB 2024-01-31 22:00:22
|
57
98
|
```
|
58
99
|
|
59
100
|
|
@@ -68,18 +109,19 @@ $ live_set -11 -f path/to/my_set.als
|
|
68
109
|
|
69
110
|
## Development
|
70
111
|
|
71
|
-
After checking out this git repository, install dependencies by
|
112
|
+
After checking out this git repository, install dependencies by running `bin/setup`:
|
72
113
|
|
73
|
-
```
|
114
|
+
```text
|
115
|
+
$ git clone https://github.com/mslinn/live_set
|
116
|
+
$ cd live_set
|
74
117
|
$ bin/setup
|
118
|
+
$ code . # Run Visual Studio Code
|
75
119
|
```
|
76
120
|
|
77
|
-
You should do the above before running Visual Studio Code.
|
78
|
-
|
79
121
|
|
80
122
|
### Run the Tests
|
81
123
|
|
82
|
-
```
|
124
|
+
```text
|
83
125
|
$ bundle exec rake test
|
84
126
|
```
|
85
127
|
|
@@ -106,7 +148,7 @@ $ bin/console
|
|
106
148
|
|
107
149
|
To install this gem onto your local machine, type:
|
108
150
|
|
109
|
-
```
|
151
|
+
```text
|
110
152
|
$ bundle exec rake install
|
111
153
|
```
|
112
154
|
|
data/als_notes.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# ALS Filetype Notes
|
2
|
+
|
3
|
+
```xml
|
4
|
+
$ zcat '/mnt/e/media/Ableton/Projects/fu Project/fu.als' | less -N
|
5
|
+
3 <LiveSet>
|
6
|
+
8 <Tracks>
|
7
|
+
9 <AudioTrack Id="8">
|
8
|
+
44 <DeviceChain>
|
9
|
+
247 <MainSequencer>
|
10
|
+
278 <ClipSlotList>
|
11
|
+
279 <ClipSlot Id="14">
|
12
|
+
280 <LomId Value="0" />
|
13
|
+
281 <ClipSlot>
|
14
|
+
282 <Value>
|
15
|
+
283 <AudioClip Id="0" Time="0">
|
16
|
+
353 <SampleRef>
|
17
|
+
354 <FileRef>
|
18
|
+
355 <RelativePathType Value="3" />
|
19
|
+
356 <RelativePath Value="Samples/Imported/song1.mp3" />
|
20
|
+
357 <Path Value="E:/media/Ableton/Projects/fu Project/Samples/Imported/song1.mp3" />
|
21
|
+
358 <Type Value="1" />
|
22
|
+
359 <LivePackName Value="" />
|
23
|
+
360 <LivePackId Value="" />
|
24
|
+
361 <OriginalFileSize Value="3626186" /> # Bytes
|
25
|
+
362 <OriginalCrc Value="47766" />
|
26
|
+
363 </FileRef>
|
27
|
+
364 <LastModDate Value="1707598944" /> # Epoch timestamp -> Sat Feb 10 16:02:24 EST 2024
|
28
|
+
365 <SourceContext />
|
29
|
+
366 <SampleUsageHint Value="0" />
|
30
|
+
367 <DefaultDuration Value="10809120" />
|
31
|
+
368 <DefaultSampleRate Value="48000" />
|
32
|
+
369 </SampleRef>
|
33
|
+
```
|
34
|
+
|
35
|
+
|
36
|
+
```text
|
37
|
+
@audiotrack = LiveSet.Tracks.AudioTrack.
|
38
|
+
DeviceChain.MainSequencer.ClipSlotList.ClipSlot.children.
|
39
|
+
ClipSlot.Value.children. # Passed to LiveAudioClip
|
40
|
+
SampleRef.FileRef
|
41
|
+
Path['Value'] # Absolute path
|
42
|
+
RelativePath['Value']
|
43
|
+
OriginalFileSize['Value'] # Bytes
|
44
|
+
```
|
45
|
+
|
46
|
+
|
47
|
+
Clip paths can look like this:
|
48
|
+
|
49
|
+
```text
|
50
|
+
# @audio_track.DeviceChain.FreezeSequencer.ClipSlotList.ClipSlot
|
51
|
+
# @audio_track.DeviceChain.Mixer.ClipSlotList.ClipSlot
|
52
|
+
# @audio_track.xpath('//Path').map{|x| x['Value']}.uniq # Gives results like:
|
53
|
+
# "E:/media/Ableton/Projects/fu Project/Samples/Processed/Freeze/Freeze Guitar [2024-03-12 133812].wav"
|
54
|
+
# "E:/media/Ableton/Projects/fu Project/Samples/Imported/smooth_operator_horns.mp3"
|
55
|
+
# "/Reverb Default.adv"
|
56
|
+
# "/Users/nsh/Library/Application Support/Ableton/Live 11 Core Library/Devices/Audio Effects/Simple Delay/Dotted Eighth Note.adv"
|
57
|
+
# "E:/media/Ableton/Projects/fu Project/Audio Effects/Color Limiter/Ableton Folder Info/Color Limiter.amxd"
|
58
|
+
```
|
data/exe/als_delta
ADDED
data/exe/live_set
CHANGED
@@ -5,11 +5,4 @@ if ENV['VO_DEBUGGING']
|
|
5
5
|
else
|
6
6
|
require 'live_set'
|
7
7
|
end
|
8
|
-
|
9
|
-
require 'colorator'
|
10
|
-
require_relative '../lib/options'
|
11
|
-
|
12
|
-
def common(command)
|
13
|
-
@options = parse_options command
|
14
|
-
help_show 'Ableton Live set name must be provided.' if ARGV.empty?
|
15
|
-
end
|
8
|
+
live_set
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'highline'
|
4
|
+
require 'shellwords'
|
5
|
+
require_relative '../common/run'
|
6
|
+
|
7
|
+
class AlsDelta
|
8
|
+
include Run
|
9
|
+
|
10
|
+
def initialize(set_name, **options)
|
11
|
+
@options = options
|
12
|
+
@loglevel = "-loglevel #{@options[:loglevel]}"
|
13
|
+
@loglevel += ' -stats' unless @options[:loglevel] == 'quiet'
|
14
|
+
@set_name = set_name
|
15
|
+
|
16
|
+
@set_dir = File.dirname set_name
|
17
|
+
@set_name_only = File.basename set_name, '.als'
|
18
|
+
end
|
19
|
+
|
20
|
+
def backup_name
|
21
|
+
@backup_name = if @options[:keep]
|
22
|
+
timestamp = Time.now.getutc.strftime '%Y-%m-%d_%H:%M:%S'
|
23
|
+
"#{@set_dir}/#{@set_name_only}_#{timestamp}.als"
|
24
|
+
else
|
25
|
+
"#{@set_dir}/#{@set_name_only}_backup.als"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def backup_set
|
30
|
+
@backup_name = backup_name
|
31
|
+
puts "Backing up Live set to '#{@backup_name}'"
|
32
|
+
FileUtils.cp @set_name, @backup_name
|
33
|
+
end
|
34
|
+
|
35
|
+
def cleanup
|
36
|
+
return if @options[:keep] || !@backup_name
|
37
|
+
return unless File.exist? @backup_name
|
38
|
+
|
39
|
+
puts "Deleting '#{@backup_name}'"
|
40
|
+
File.delete @backup_name
|
41
|
+
end
|
42
|
+
|
43
|
+
def show
|
44
|
+
backup_set
|
45
|
+
loop do
|
46
|
+
puts 'Press any key to display the changes to the Live set XML file, or press Esc to exit.'
|
47
|
+
character = $stdin.getch
|
48
|
+
exit if character.ord == 27
|
49
|
+
output = run_capture_stdout "GZIP=--no-name zdiff #{@set_name.shellescape} #{@backup_name.shellescape}"
|
50
|
+
if output.empty?
|
51
|
+
puts 'There were no changes to the saved live set.'
|
52
|
+
else
|
53
|
+
puts output.join("\n")
|
54
|
+
backup_set
|
55
|
+
end
|
56
|
+
rescue StandardError => e
|
57
|
+
puts e
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'colorator'
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze unless defined? VERBOSITY
|
5
|
+
|
6
|
+
def common(command)
|
7
|
+
@options = parse_options command
|
8
|
+
help_als_delta 'An Ableton Live set name must be provided.' if ARGV.empty?
|
9
|
+
end
|
10
|
+
|
11
|
+
def help_als_delta(msg = nil)
|
12
|
+
printf "Error: #{msg}\n\n".yellow unless msg.nil?
|
13
|
+
msg = <<~END_HELP
|
14
|
+
ls_delta: Shows changes to an .als file.
|
15
|
+
Press enter to compare with previous version.
|
16
|
+
|
17
|
+
Syntax: ls_delta OPTIONS PATH_TO_ALS_FILE
|
18
|
+
|
19
|
+
Environment variables used in PATH_TO_ALS_FILE are expanded.
|
20
|
+
|
21
|
+
Options are:
|
22
|
+
-k Keep each backup (default is to delete them)
|
23
|
+
-h Display this help message.
|
24
|
+
|
25
|
+
Example:
|
26
|
+
ls_delta $path/to/my_v12_set.als
|
27
|
+
END_HELP
|
28
|
+
printf msg.cyan
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_options
|
33
|
+
options = { keep: false, loglevel: 'warning' }
|
34
|
+
opts = do_parse
|
35
|
+
opts.order!(into: options)
|
36
|
+
|
37
|
+
help_als_delta "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." \
|
38
|
+
if options[:verbose] && !options[:verbose] in VERBOSITY
|
39
|
+
|
40
|
+
options
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def do_parse
|
46
|
+
OptionParser.new do |parser|
|
47
|
+
parser.program_name = File.basename __FILE__
|
48
|
+
@parser = parser
|
49
|
+
|
50
|
+
parser.on('-k', '--keep', 'Keep all backups')
|
51
|
+
parser.on('-l', '--loglevel LOGLEVEL', Integer, "Logging level (#{VERBOSITY.join ', '})")
|
52
|
+
parser.on('-v', '--verbose VERBOSE', 'Verbosity')
|
53
|
+
|
54
|
+
parser.on_tail('-h', '--help', 'Show this message') do
|
55
|
+
help_als_delta
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# require 'colorator'
|
2
|
+
|
3
|
+
# def common(command)
|
4
|
+
# @options = parse_options command
|
5
|
+
# help_live_set 'The path for the Ableton Set must be provided.' if ARGV.empty?
|
6
|
+
# end
|
7
|
+
|
8
|
+
# def show
|
9
|
+
# common :show
|
10
|
+
# help_live_set "Too many parameters specified.\n#{ARGV}" if ARGV.length > 2
|
11
|
+
|
12
|
+
# set_name = ARGV.join ' '
|
13
|
+
# help "#{set_name} does not exist" unless File.exist? set_name
|
14
|
+
# help "#{set_name} is a directory" if File.directory? set_name
|
15
|
+
|
16
|
+
# LiveSet.new(set_name, **@options).show
|
17
|
+
# end
|
data/lib/common/run.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Run
|
4
|
+
# @return true if the command succeeded
|
5
|
+
def run(command)
|
6
|
+
# printf "Run #{command}\n".yellow
|
7
|
+
$stdout.sync = true
|
8
|
+
system(*command)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return String array containing resulting lines of running command
|
12
|
+
def run_capture_stdout(command)
|
13
|
+
# printf "Run #{command}\n".yellow
|
14
|
+
stdout_str, status = Open3.capture2 command
|
15
|
+
unless status.success?
|
16
|
+
printf "Error: #{command} returned #{status}"
|
17
|
+
exit status.to_i
|
18
|
+
end
|
19
|
+
stdout_str.strip.split
|
20
|
+
end
|
21
|
+
end
|
data/lib/common/util.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
class Array
|
2
|
+
# Convert an Array of Array into a String, formatted as a table.
|
3
|
+
def columnize(separator = ' ')
|
4
|
+
each { |row| row.is_a? Array or raise NoMethodError, 'Must be called on an Array of Array.' }
|
5
|
+
result = ''
|
6
|
+
l = []
|
7
|
+
each { |row| row.each_with_index { |f, i| l[i] = [l[i] || 0, f.to_s.length].max } }
|
8
|
+
each do |row|
|
9
|
+
row.each_with_index { |f, i| result << "#{f.to_s.ljust l[i]}#{separator}" }
|
10
|
+
result << "\n"
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class String
|
17
|
+
def to_bool
|
18
|
+
return true if casecmp('true').zero?
|
19
|
+
return false if casecmp('false').zero?
|
20
|
+
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def expand_env
|
25
|
+
gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
|
26
|
+
ENV.fetch(Regexp.last_match(1), nil)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def human_file_size(number, decimal_places = 1)
|
32
|
+
ByteSize.new(number).to_s(decimal_places)
|
33
|
+
end
|
34
|
+
|
35
|
+
def require_directory(dir)
|
36
|
+
Dir[File.join(dir, '*.rb')].sort.each do |file|
|
37
|
+
require file
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class AllTracks
|
2
|
+
def initialize(tracks)
|
3
|
+
@tracks = tracks
|
4
|
+
@track_instances = tracks.map { |track| LiveAudioTrack.new track }
|
5
|
+
end
|
6
|
+
|
7
|
+
def all_frozen
|
8
|
+
@track_instances.all?(&:frozen)
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
if @tracks.empty?
|
13
|
+
'No tracks'
|
14
|
+
else
|
15
|
+
summary + ' ' + @track_instances.map { |x| x.show_track(all_frozen) }.join("\n ") # rubocop:disable Style/StringConcatenation
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def summary
|
20
|
+
all_frozen_msg = all_frozen ? ' (All are frozen)'.yellow : ''
|
21
|
+
push_warning = all_frozen ? '' : "\nWarning: some tracks are not frozen, so this set should not be transferred to Push 3 Standalone.".red
|
22
|
+
total_size = @track_instances.sum(&:track_size)
|
23
|
+
size_warning = if total_size >= 2_000_000_000
|
24
|
+
"\nWarning: This set is too large to be frozen and too large too large to transfer to Push 3 Standalone.".red
|
25
|
+
else
|
26
|
+
''
|
27
|
+
end
|
28
|
+
total_set_size = "Total set size: #{human_file_size total_size}".yellow
|
29
|
+
<<~END_MSG
|
30
|
+
#{total_set_size}#{size_warning}#{push_warning}
|
31
|
+
#{@track_instances.count} tracks#{all_frozen_msg}:
|
32
|
+
END_MSG
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class LiveAudioClip
|
4
|
+
attr_reader :file_size
|
5
|
+
|
6
|
+
# @param audio_clip is an inner <ClipSlot/> element from an Ableton Live .als file as parsed by Nokogiri
|
7
|
+
def initialize(id, need_refreeze, clip_slot)
|
8
|
+
@id = id
|
9
|
+
@need_refreeze = need_refreeze
|
10
|
+
|
11
|
+
@audio_clip = clip_slot.Value.AudioClip
|
12
|
+
@file_ref = @audio_clip.SampleRef.FileRef
|
13
|
+
@last_modified = Time.at(@audio_clip.SampleRef.LastModDate['Value'].to_i).utc.to_datetime
|
14
|
+
|
15
|
+
@absolute_path = @file_ref.Path['Value']
|
16
|
+
@file_size = @file_ref.OriginalFileSize['Value'].to_i
|
17
|
+
@file_type = @file_ref.Type['Value'] # What do these values mean?
|
18
|
+
@live_pack_name = @file_ref.LivePackName['Value']
|
19
|
+
@relative_path = @file_ref.RelativePath['Value']
|
20
|
+
@relative_path_type = @file_ref.RelativePathType['Value'] # What do these values mean?
|
21
|
+
end
|
22
|
+
|
23
|
+
def show_clip
|
24
|
+
[" #{@relative_path}", human_file_size(@file_size).rjust(8, ' '), @last_modified.strftime('%Y-%m-%d %H:%M:%S')]
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class LiveAudioTrack
|
2
|
+
attr_reader :frozen, :track_size
|
3
|
+
|
4
|
+
# @param audio_track is an <AudioTrack/> element from an Ableton Live .als file as parsed by Nokogiri
|
5
|
+
def initialize(audio_track)
|
6
|
+
@audio_track = audio_track
|
7
|
+
@id = @audio_track['Id']
|
8
|
+
|
9
|
+
@frozen = @audio_track.Freeze['Value'].to_bool
|
10
|
+
|
11
|
+
@clip_slots = @audio_track.DeviceChain.MainSequencer.ClipSlotList
|
12
|
+
@audio_clips = @clip_slots.ClipSlot.map do |clip_slot|
|
13
|
+
clip_slot_id = clip_slot['Id']
|
14
|
+
need_refreeze = clip_slot.NeedRefreeze['Value'].to_bool
|
15
|
+
inner_clip_slot = clip_slot.ClipSlot
|
16
|
+
LiveAudioClip.new clip_slot_id, need_refreeze, inner_clip_slot if inner_clip_slot.respond_to?(:Value) && !inner_clip_slot.Value.children.empty?
|
17
|
+
end
|
18
|
+
@audio_clips.compact!
|
19
|
+
@track_size = @audio_clips.sum(&:file_size) || 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def show_track(all_frozen)
|
23
|
+
name = @audio_track.Name.EffectiveName['Value']
|
24
|
+
frozen = !all_frozen && @audio_track.frozen? ? ' **frozen**' : ''
|
25
|
+
"Track '#{name}'#{frozen} (#{@audio_clips.length} clips, totaling #{human_file_size @track_size})\n" + show_clips
|
26
|
+
end
|
27
|
+
|
28
|
+
def show_clips
|
29
|
+
return '' if @track_size.zero?
|
30
|
+
|
31
|
+
result = @audio_clips.map(&:show_clip)
|
32
|
+
result.columnize ' '
|
33
|
+
end
|
34
|
+
end
|
@@ -1,14 +1,11 @@
|
|
1
|
+
require 'bytesize'
|
1
2
|
require 'nokogiri'
|
3
|
+
require 'pathname'
|
2
4
|
require 'zlib'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
return false if casecmp('false').zero?
|
8
|
-
|
9
|
-
nil
|
10
|
-
end
|
11
|
-
end
|
6
|
+
# See https://www.ionos.ca/digitalguide/websites/web-development/xpath-tutorial/
|
7
|
+
# See https://www.scrapingbee.com/webscraping-questions/selenium/how-to-find-elements-by-xpath-selenium
|
8
|
+
# See https://nokogiri.org/tutorials/searching_a_xml_html_document.html#slop-1
|
12
9
|
|
13
10
|
class LiveSet
|
14
11
|
def initialize(set_name, **options)
|
@@ -21,9 +18,14 @@ class LiveSet
|
|
21
18
|
|
22
19
|
@ableton = @xml_doc.Ableton
|
23
20
|
@minor_version = @ableton['MinorVersion']
|
21
|
+
|
22
|
+
@live_set = @ableton.LiveSet
|
23
|
+
@tracks = AllTracks.new @live_set.Tracks.AudioTrack
|
24
|
+
@scenes = @live_set.Scenes.map { |scene| LiveScene.new scene }
|
24
25
|
end
|
25
26
|
|
26
27
|
def show
|
28
|
+
check_abelton_project_info
|
27
29
|
puts <<~END_SHOW
|
28
30
|
#{@set_name}
|
29
31
|
Created by #{@ableton['Creator']}
|
@@ -31,16 +33,8 @@ class LiveSet
|
|
31
33
|
Minor version v#{@minor_version}
|
32
34
|
SchemaChangeCount #{@ableton['SchemaChangeCount']}
|
33
35
|
Revision #{@ableton['Revision']}
|
36
|
+
#{@tracks.message}
|
34
37
|
END_SHOW
|
35
|
-
tracks = @ableton.LiveSet.Tracks.AudioTrack
|
36
|
-
track_msg = if tracks.empty?
|
37
|
-
'No tracks'
|
38
|
-
else
|
39
|
-
all_frozen = all_tracks_frozen(tracks)
|
40
|
-
track_summary(tracks) +
|
41
|
-
tracks.map { |x| show_track x, all_frozen }.join("\n ")
|
42
|
-
end
|
43
|
-
puts track_msg
|
44
38
|
end
|
45
39
|
|
46
40
|
def modify_als
|
@@ -72,25 +66,4 @@ class LiveSet
|
|
72
66
|
end
|
73
67
|
Zlib::GzipWriter.open(new_set_path) { |gz| gz.write new_contents }
|
74
68
|
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
def all_tracks_frozen(tracks)
|
79
|
-
tracks.all? { |track| track_is_frozen track }
|
80
|
-
end
|
81
|
-
|
82
|
-
def track_is_frozen(track)
|
83
|
-
track.Freeze['Value'].to_bool
|
84
|
-
end
|
85
|
-
|
86
|
-
def track_summary(tracks)
|
87
|
-
all_frozen_msg = all_tracks_frozen(tracks) ? ' (All are frozen)' : ''
|
88
|
-
"#{tracks.count} tracks#{all_frozen_msg}:\n "
|
89
|
-
end
|
90
|
-
|
91
|
-
def show_track(audio_track, all_frozen)
|
92
|
-
name = audio_track.Name.EffectiveName['Value']
|
93
|
-
frozen = !all_frozen && track_is_frozen(audio_track) ? ' **frozen**' : ''
|
94
|
-
"#{name}#{frozen}"
|
95
|
-
end
|
96
69
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'colorator'
|
2
|
+
require 'highline'
|
3
|
+
|
4
|
+
class LiveSet
|
5
|
+
private
|
6
|
+
|
7
|
+
def check_abelton_project_info
|
8
|
+
set_directory = File.realpath(File.dirname(@set_name))
|
9
|
+
api_path = File.join(set_directory, 'Ableton Project Info')
|
10
|
+
puts "Warning: '#{api_path}' is not present".red unless File.exist? api_path
|
11
|
+
puts "Warning: '#{api_path}' is not a directory".red unless Dir.exist? api_path
|
12
|
+
|
13
|
+
cur_dir = Pathname.new(set_directory).parent
|
14
|
+
while cur_dir
|
15
|
+
project_info_path = File.join(cur_dir, 'Ableton Project Info')
|
16
|
+
add_dash_suffix(project_info_path) if File.exist? project_info_path
|
17
|
+
break if cur_dir.to_path == '/'
|
18
|
+
|
19
|
+
cur_dir = cur_dir.parent
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_dash_suffix(cur_dir, path)
|
24
|
+
puts "Warning: 'Ableton Project Info' exists in parent directory '#{cur_dir.to_path}'".red
|
25
|
+
return unless HighLine.agree("\nDo you want the directory to be disabled by suffixing a dash to its name? ", character = true)
|
26
|
+
|
27
|
+
File.rename path, "#{path}-"
|
28
|
+
end
|
29
|
+
end
|
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'colorator'
|
2
2
|
require 'optparse'
|
3
3
|
|
4
|
-
VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze
|
4
|
+
VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze unless defined? VERBOSITY
|
5
5
|
|
6
|
-
def
|
6
|
+
def help_live_set(msg = nil)
|
7
7
|
printf "Error: #{msg}\n\n".yellow unless msg.nil?
|
8
8
|
msg = <<~END_HELP
|
9
|
-
|
9
|
+
Live_sete displays information about an Ableton Live set or converts a Live 12 set to Live 11 format.
|
10
|
+
If the special folder called 'Ableton Project Info' is not present in the same folder as the .als file, a warning is generated.
|
11
|
+
Similarly, if 'Ableton Project Info' is present in any parent folder, a warning is issued.
|
10
12
|
|
11
13
|
Syntax: live_set OPTIONS PATH_TO_ALS_FILE
|
12
14
|
|
@@ -14,7 +16,7 @@ def help_show(msg = nil)
|
|
14
16
|
|
15
17
|
Options are:
|
16
18
|
-11 Convert the Live 12 set to Live 11 format.
|
17
|
-
-f
|
19
|
+
-f If -11 is specified, overwrite any existing Live 11 set.
|
18
20
|
-h Display this help message.
|
19
21
|
|
20
22
|
Example:
|
@@ -29,13 +31,12 @@ def parse_options
|
|
29
31
|
opts = do_parse
|
30
32
|
opts.order!(into: options)
|
31
33
|
|
32
|
-
|
34
|
+
live_set "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." \
|
33
35
|
if options[:verbose] && !options[:verbose] in VERBOSITY
|
34
36
|
|
35
37
|
options
|
36
38
|
end
|
37
39
|
|
38
|
-
|
39
40
|
private
|
40
41
|
|
41
42
|
def do_parse
|
@@ -47,10 +48,10 @@ def do_parse
|
|
47
48
|
parser.on('-f', '--force', 'Overwrite the output set if it already exists')
|
48
49
|
parser.on('-s', '--show', 'Display information about the Ableton Live set')
|
49
50
|
parser.on('-l', '--loglevel LOGLEVEL', Integer, "Logging level (#{VERBOSITY.join ', '})")
|
50
|
-
parser.on('-v', '--verbose VERBOSE', '
|
51
|
+
parser.on('-v', '--verbose VERBOSE', 'Verbosity')
|
51
52
|
|
52
53
|
parser.on_tail('-h', '--help', 'Show this message') do
|
53
|
-
|
54
|
+
live_set
|
54
55
|
end
|
55
56
|
end
|
56
57
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class LiveScene
|
2
|
+
# @param live_scene is an XML element as parsed by Nokogiri
|
3
|
+
def initialize(live_scene)
|
4
|
+
@live_scene = live_scene
|
5
|
+
@id = @live_scene['Id']
|
6
|
+
@name = @live_scene.Name['Value']
|
7
|
+
@tempo = @live_scene.Tempo['Value'] if @live_scene.IsTempoEnabled['Value'].to_bool
|
8
|
+
end
|
9
|
+
end
|
data/lib/live_set/version.rb
CHANGED
data/lib/live_set.rb
CHANGED
@@ -1,29 +1,50 @@
|
|
1
|
-
require_relative 'live_set/
|
1
|
+
require_relative 'live_set/live_set_options'
|
2
|
+
require_relative 'common/util'
|
3
|
+
require_directory "#{__dir__}/common"
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require file unless file.end_with?('/live_set.rb')
|
5
|
+
def common(command)
|
6
|
+
@options = parse_options command
|
7
|
+
help_live_set 'Ableton Live set name must be provided.' if ARGV.empty?
|
7
8
|
end
|
8
9
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def als_delta
|
11
|
+
require_directory "#{__dir__}/als_delta"
|
12
|
+
|
13
|
+
help_als_delta if ARGV.empty?
|
14
|
+
|
15
|
+
@options = parse_options
|
16
|
+
|
17
|
+
set_name = ARGV.join(' ').expand_env
|
18
|
+
help_als_delta "#{set_name} does not exist" unless File.exist? set_name
|
19
|
+
help_als_delta "#{set_name} is a directory" if File.directory? set_name
|
20
|
+
|
21
|
+
trap('SIGINT') { throw :ctrl_c }
|
22
|
+
|
23
|
+
als_delta = AlsDelta.new set_name, **@options
|
24
|
+
catch :ctrl_c do
|
25
|
+
als_delta.show
|
26
|
+
als_delta.cleanup
|
27
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
28
|
+
als_delta.cleanup
|
29
|
+
end
|
13
30
|
end
|
14
31
|
|
15
|
-
|
32
|
+
def live_set
|
33
|
+
require_directory "#{__dir__}/live_set"
|
34
|
+
|
35
|
+
help_live_set if ARGV.empty?
|
16
36
|
|
17
|
-
@options = parse_options
|
37
|
+
@options = parse_options
|
18
38
|
|
19
|
-
set_name =
|
20
|
-
|
21
|
-
|
39
|
+
set_name = ARGV.join(' ').expand_env
|
40
|
+
help_live_set "#{set_name} does not exist" unless File.exist? set_name
|
41
|
+
help_live_set "#{set_name} is a directory" if File.directory? set_name
|
22
42
|
|
23
|
-
live_set = LiveSet.new set_name, **@options
|
43
|
+
live_set = LiveSet.new set_name, **@options
|
24
44
|
|
25
|
-
if @options[:convert11]
|
26
|
-
|
27
|
-
else
|
28
|
-
|
45
|
+
if @options[:convert11]
|
46
|
+
live_set.modify_als
|
47
|
+
else
|
48
|
+
live_set.show
|
49
|
+
end
|
29
50
|
end
|
data/live_set.gemspec
CHANGED
@@ -5,7 +5,7 @@ Gem::Specification.new do |spec|
|
|
5
5
|
|
6
6
|
spec.authors = ['Mike Slinn']
|
7
7
|
spec.bindir = 'exe'
|
8
|
-
spec.executables = %w[live_set]
|
8
|
+
spec.executables = %w[als_delta live_set]
|
9
9
|
spec.description = <<~END_DESC
|
10
10
|
Reports the version of an Ableton Live set, and optionally changes it to v11 format.
|
11
11
|
END_DESC
|
@@ -31,6 +31,8 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.summary = 'Reports the version of an Ableton Live set, and optionally changes it to v11 format.'
|
32
32
|
spec.version = LiveSetVersion::VERSION
|
33
33
|
|
34
|
+
spec.add_dependency 'bytesize'
|
34
35
|
spec.add_dependency 'colorator'
|
36
|
+
spec.add_dependency 'highline'
|
35
37
|
spec.add_dependency 'nokogiri'
|
36
38
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: live_set
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Slinn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bytesize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: colorator
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -24,6 +38,20 @@ dependencies:
|
|
24
38
|
- - ">="
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: highline
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: nokogiri
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -45,6 +73,7 @@ description: 'Reports the version of an Ableton Live set, and optionally changes
|
|
45
73
|
email:
|
46
74
|
- mslinn@mslinn.com
|
47
75
|
executables:
|
76
|
+
- als_delta
|
48
77
|
- live_set
|
49
78
|
extensions: []
|
50
79
|
extra_rdoc_files: []
|
@@ -53,13 +82,23 @@ files:
|
|
53
82
|
- CHANGELOG.md
|
54
83
|
- README.md
|
55
84
|
- Rakefile
|
85
|
+
- als_notes.md
|
86
|
+
- exe/als_delta
|
56
87
|
- exe/live_set
|
57
|
-
- lib/
|
88
|
+
- lib/als_delta/als_delta_class.rb
|
89
|
+
- lib/als_delta/als_delta_options.rb
|
90
|
+
- lib/common/common.rb
|
91
|
+
- lib/common/run.rb
|
92
|
+
- lib/common/util.rb
|
58
93
|
- lib/live_set.rb
|
94
|
+
- lib/live_set/live_set_all_tracks.rb
|
95
|
+
- lib/live_set/live_set_audio_clip.rb
|
96
|
+
- lib/live_set/live_set_audio_track.rb
|
97
|
+
- lib/live_set/live_set_class.rb
|
98
|
+
- lib/live_set/live_set_class_private.rb
|
99
|
+
- lib/live_set/live_set_options.rb
|
100
|
+
- lib/live_set/live_set_scene.rb
|
59
101
|
- lib/live_set/version.rb
|
60
|
-
- lib/llive_set_class.rb
|
61
|
-
- lib/options.rb
|
62
|
-
- lib/util.rb
|
63
102
|
- live_set.gemspec
|
64
103
|
homepage: https://github.com/mslinn/live_set
|
65
104
|
licenses:
|
data/lib/common.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'colorator'
|
2
|
-
require_relative 'options'
|
3
|
-
|
4
|
-
def common(command)
|
5
|
-
@options = parse_options command
|
6
|
-
help_show 'The path for the Ableton Set must be provided.' if ARGV.empty?
|
7
|
-
end
|
8
|
-
|
9
|
-
def show
|
10
|
-
common :show
|
11
|
-
help_show "Too many parameters specified.\n#{ARGV}" if ARGV.length > 2
|
12
|
-
|
13
|
-
set_name = ARGV.join ' '
|
14
|
-
help "#{set_name} does not exist" unless File.exist? set_name
|
15
|
-
help "#{set_name} is a directory" if File.directory? set_name
|
16
|
-
|
17
|
-
LiveSet.new(set_name, **@options).show
|
18
|
-
end
|