live_set 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40ccc8116bfe826b52087a95ccdda582b948c7312be95eb282f17c5da7a24c86
4
- data.tar.gz: e97386a24aa9dbcee91080e07cd2c9cc4331b65dab5b5080cbd0665b058de37a
3
+ metadata.gz: 383208bd53c5b54e254945faa8d79ae100b78a1dd34fa3820abdaec8dc1b2854
4
+ data.tar.gz: b6d6ae70303f7ff3a2c43039a27ca2424c8296dcf6cf36d3ff9a572d4d285b97
5
5
  SHA512:
6
- metadata.gz: 90def6a620d141ee67449c7c4f66345d65aea8cad97a5ce0a37a8095e223b2ffc214825a5c10e81aa96e62c141fb6ba0df05cc0f3bb56bbf9f115238cad4fed3
7
- data.tar.gz: 1f2e4ffa6e835d7021fe5e2d124a758b9cc629071cc1f5d1517cf20915640932966a433f867b92dc1083261aecb8b8fc748c5031dadaef0935a327af4ef9760a
6
+ metadata.gz: 1e2078593ba88bb503c9a2a712bc85362f05b31ceec83a3276785a3b01961d8c85b6c6cc3bf7822670e1e4bb4372dfb214225ab1cbe927f9e6ffab541539560a
7
+ data.tar.gz: 408994bfca9f20dec4df040cee40268da9339549963ee10da80725f2dc4f619ec01db53d1df4e0c02389aeee6224f8c7ecf1bb21044ce24878a56fb5177a2039
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.2.0
4
+
5
+ * Checks `Ableton Project Info` validity all up the directory tree.
6
+ * Prompts user if they want the extra directory renamed to `Ableton Project Info-`.
7
+ * Reports the clips in the set, and their sizes.
8
+ * Generates a warning if the total size of the clips might cause freezing to fail.
9
+
10
+
3
11
  ## 0.1.0
4
12
 
5
13
  * 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 report the version of an Ableton Live set,
4
- and it can changes an Ableton Live set from Live 12 format to Live 11 format.
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
- ```shell
30
+ ```text
21
31
  $ live_set
22
- Displays information about a Live set or converts a Live 12 set to Live 11 format.
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 set:
40
-
41
- ```shell
42
- $ live_set $path/to/my_set.als
43
- /mnt/c/media/Ableton/Projects/fu Project/fu.als
44
- Created by Live v11.0_11300
45
- Major version 11.0_11300
46
- Revision 5ac24cad7c51ea0671d49e6b4885371f15b57c1e
47
- SchemaChangeCount 3
48
- Tracks:
49
- Drums / complete
50
- Bass
51
- Guitar
52
- Horns
53
- Keys
54
- Vocal Backing
55
- Vocal Lead
56
- Strings
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 typing:
112
+ After checking out this git repository, install dependencies by running `bin/setup`:
72
113
 
73
- ```shell
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
- ```shell
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
- ```shell
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
@@ -0,0 +1,8 @@
1
+ #!/bin/env ruby
2
+
3
+ if ENV['VO_DEBUGGING']
4
+ require_relative '../lib/live_set'
5
+ else
6
+ require 'live_set'
7
+ end
8
+ als_delta
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,48 @@
1
+ require 'date'
2
+ require 'fileutils'
3
+ require_relative '../common/run'
4
+
5
+ class AlsDelta
6
+ include Run
7
+
8
+ def initialize(set_name, **options)
9
+ @options = options
10
+ @loglevel = "-loglevel #{@options[:loglevel]}"
11
+ @loglevel += ' -stats' unless @options[:loglevel] == 'quiet'
12
+ @set_name = set_name
13
+
14
+ @set_dir = File.dirname set_name
15
+ @set_name_only = File.basename set_name, '.als'
16
+ end
17
+
18
+ def backup_name
19
+ @backup_name = if @options[:keep]
20
+ timestamp = Time.now.getutc.strftime '%Y-%m-%d_%H:%M:%S'
21
+ "#{@set_dir}/#{@set_name_only}_#{timestamp}.als"
22
+ else
23
+ "#{@set_dir}/#{@set_name_only}_backup.als"
24
+ end
25
+ end
26
+
27
+ def backup_set
28
+ @backup_name = backup_name
29
+ FileUtils.cp @set_name, @backup_name
30
+ end
31
+
32
+ def cleanup
33
+ return if @options[:keep] || !@backup_name
34
+ return unless File.exist? @backup_name
35
+
36
+ puts "Deleting #{@backup_name}"
37
+ File.delete @backup_name
38
+ end
39
+
40
+ def show
41
+ output = run_capture_stdout "zdiff '#{@backup_name}' '#{@set_name}'"
42
+ if output.empty?
43
+ puts 'There were no changes to the saved live set.'
44
+ else
45
+ puts output.join("\n")
46
+ end
47
+ end
48
+ 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
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
@@ -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
- class String
5
- def to_bool
6
- return true if casecmp('true').zero?
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,20 @@
1
+ class LiveSet
2
+ private
3
+
4
+ def check_abelton_project_info
5
+ set_directory = File.realpath(File.dirname(@set_name))
6
+ api_path = File.join(set_directory, 'Ableton Project Info')
7
+ puts "Warning: '#{api_path}' is not present".red unless File.exist? api_path
8
+ puts "Warning: '#{api_path}' is not a directory".red unless Dir.exist? api_path
9
+
10
+ cur_dir = Pathname.new(set_directory).parent
11
+ while cur_dir
12
+ if File.exist? File.join(cur_dir, 'Ableton Project Info')
13
+ puts "Warning: 'Ableton Project Info' exists in parent directory '#{cur_dir.to_path}'".red
14
+ end
15
+ break if cur_dir.to_path == '/'
16
+
17
+ cur_dir = cur_dir.parent
18
+ end
19
+ end
20
+ end
@@ -3,10 +3,12 @@ require 'optparse'
3
3
 
4
4
  VERBOSITY = %w[trace debug verbose info warning error fatal panic quiet].freeze
5
5
 
6
- def help_show(msg = nil)
6
+ def live_set(msg = nil)
7
7
  printf "Error: #{msg}\n\n".yellow unless msg.nil?
8
8
  msg = <<~END_HELP
9
- Displays information about a Live set or converts a Live 12 set to Live 11 format.
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 Overwrite any existing Live 11 set.
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
- help_show "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." \
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', 'Zoom percentage')
51
+ parser.on('-v', '--verbose VERBOSE', 'Verbosity')
51
52
 
52
53
  parser.on_tail('-h', '--help', 'Show this message') do
53
- help_show
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
@@ -1,3 +1,3 @@
1
1
  module LiveSetVersion
2
- VERSION = '0.1.0'.freeze unless defined? VERSION
2
+ VERSION = '0.2.0'.freeze unless defined? VERSION
3
3
  end
data/lib/live_set.rb CHANGED
@@ -1,29 +1,51 @@
1
- require_relative 'live_set/version'
1
+ require_relative 'live_set/live_set_options'
2
+ require_relative 'common/util'
3
+ require_directory "#{__dir__}/common"
2
4
 
3
- # Require all Ruby files in 'lib/', except this file
4
- Dir[File.join(__dir__, '*.rb')].sort.each do |file|
5
- # puts "Requiring #{file}"
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 help(msg = nil)
10
- puts "Error: #{msg}" if msg
11
- puts 'List files in ableton set'
12
- exit 1
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.backup_set
26
+ als_delta.show
27
+ als_delta.cleanup
28
+ rescue Exception # rubocop:disable Lint/RescueException
29
+ als_delta.cleanup
30
+ end
13
31
  end
14
32
 
15
- help_show if ARGV.empty?
33
+ def live_set
34
+ require_directory "#{__dir__}/live_set"
35
+
36
+ help_live_set if ARGV.empty?
16
37
 
17
- @options = parse_options
38
+ @options = parse_options
18
39
 
19
- set_name = expand_env ARGV.join(' ')
20
- help_show "#{set_name} does not exist" unless File.exist? set_name
21
- help_show "#{set_name} is a directory" if File.directory? set_name
40
+ set_name = ARGV.join(' ').expand_env
41
+ help_live_set "#{set_name} does not exist" unless File.exist? set_name
42
+ help_live_set "#{set_name} is a directory" if File.directory? set_name
22
43
 
23
- live_set = LiveSet.new set_name, **@options
44
+ live_set = LiveSet.new set_name, **@options
24
45
 
25
- if @options[:convert11]
26
- live_set.modify_als
27
- else
28
- live_set.show
46
+ if @options[:convert11]
47
+ live_set.modify_als
48
+ else
49
+ live_set.show
50
+ end
29
51
  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,7 @@ 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'
35
36
  spec.add_dependency 'nokogiri'
36
37
  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.0
4
+ version: 0.2.0
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-14 00:00:00.000000000 Z
11
+ date: 2024-03-20 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
@@ -45,6 +59,7 @@ description: 'Reports the version of an Ableton Live set, and optionally changes
45
59
  email:
46
60
  - mslinn@mslinn.com
47
61
  executables:
62
+ - als_delta
48
63
  - live_set
49
64
  extensions: []
50
65
  extra_rdoc_files: []
@@ -53,13 +68,23 @@ files:
53
68
  - CHANGELOG.md
54
69
  - README.md
55
70
  - Rakefile
71
+ - als_notes.md
72
+ - exe/als_delta
56
73
  - exe/live_set
57
- - lib/common.rb
74
+ - lib/als_delta/ als_delta_class.rb
75
+ - lib/als_delta/als_delta_options.rb
76
+ - lib/common/common.rb
77
+ - lib/common/run.rb
78
+ - lib/common/util.rb
58
79
  - lib/live_set.rb
80
+ - lib/live_set/live_set_all_tracks.rb
81
+ - lib/live_set/live_set_audio_clip.rb
82
+ - lib/live_set/live_set_audio_track.rb
83
+ - lib/live_set/live_set_class.rb
84
+ - lib/live_set/live_set_class_private.rb
85
+ - lib/live_set/live_set_options.rb
86
+ - lib/live_set/live_set_scene.rb
59
87
  - lib/live_set/version.rb
60
- - lib/llive_set_class.rb
61
- - lib/options.rb
62
- - lib/util.rb
63
88
  - live_set.gemspec
64
89
  homepage: https://github.com/mslinn/live_set
65
90
  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
data/lib/util.rb DELETED
@@ -1,5 +0,0 @@
1
- def self.expand_env(str)
2
- str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
3
- ENV.fetch(Regexp.last_match(1), nil)
4
- end
5
- end