ffi-openmpt 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1051eb2d63c430d769cba4769ad283817d1b615276d5529d6bd4e4f253ceb57e
4
- data.tar.gz: 32518a3a258ac8bc43b9da89aa91f750f874dbc70349c7e541f3d1637e67a583
3
+ metadata.gz: 06607b1145d777c793ee96ca49e70132f74dd3ec2289957909519a109a64f7d7
4
+ data.tar.gz: f5ef317907e2ff4866a1d0832ba64b820f409dc03dce32613321bc3767cc5c9b
5
5
  SHA512:
6
- metadata.gz: 6f5f6fa54e93a5e93e0f8e8364a2cc00b8a285abd168546269783b7c877f6b395462f255f2ac33a5a74e2758ac5dac526dfdffac2544486ebf913a51f7e45a2a
7
- data.tar.gz: 1dd220d1b5d7271b11e6d8daff950f017af642bf67ca1716d164d3b9516cd8bdd3ff6995ed0a418edb22ad4869380546c91c7fb67e7933536a0d0cb545d23da2
6
+ metadata.gz: 5a03b9aa048cfcb588e36b147b396d04653fb5d55780d2f6ff879954ae7d0a783ee414c003477f3b5e3693c930baad0d400c755a428bc5aa2528264f9c7064fb
7
+ data.tar.gz: bd5d1f18bdaa730e22b38f281d2281c3eb544631a79fedf10dc32bc1f7e897afe3a54da726c83cc7f19398f527fc9810f5038ff9df0a1dd04ec1b86663c8a86c
@@ -22,6 +22,14 @@ Layout/EmptyLinesAroundClassBody:
22
22
  Layout/EmptyLinesAroundModuleBody:
23
23
  Enabled: false
24
24
 
25
+ # Don't enforce things like #positive? due to older rubies not supporting it
26
+ Style/NumericPredicate:
27
+ EnforcedStyle: comparison
28
+
29
+ Style/MixinUsage:
30
+ Exclude:
31
+ - 'examples/**/*'
32
+
25
33
  # Allow compact child definitions in the tests for brevity.
26
34
  Style/ClassAndModuleChildren:
27
35
  Exclude:
@@ -34,10 +42,24 @@ Metrics/AbcSize:
34
42
 
35
43
  # Set a more reasonable method length and ignore failures in the tests.
36
44
  Metrics/MethodLength:
45
+ Max: 15
37
46
  Exclude:
38
47
  - 'test/**/*.rb'
39
48
 
40
49
  # Set a more reasonable class length and ignore failures in the tests.
41
50
  Metrics/ClassLength:
51
+ Max: 200
52
+ Exclude:
53
+ - 'test/**/*.rb'
54
+
55
+ # Set a more reasonable module length and ignore failures in the tests.
56
+ # Also allow the API module to be very long as it just maps lots of methods.
57
+ Metrics/ModuleLength:
58
+ Exclude:
59
+ - 'lib/ffi/openmpt/api.rb'
60
+ - 'test/**/*.rb'
61
+
62
+ # Set a more reasonable block length and ignore failures in the tests.
63
+ Metrics/BlockLength:
42
64
  Exclude:
43
65
  - 'test/**/*.rb'
data/CHANGES.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # Changes log for the Ruby OpenMPT library (ffi-openmpt)
2
2
 
3
+ ## Version 0.2.0
4
+
5
+ * Add a Gem version badge to the README.md.
6
+ * Wrap the libopenmpt module probing functions.
7
+ * Enforce comparisons, not predicates, for older rubies.
8
+ * Wrap libopenmpt informational function: get subsongs.
9
+ * Wrap libopenmpt informational function: get channels.
10
+ * Wrap libopenmpt informational function: get orders.
11
+ * Wrap libopenmpt informational function: get patterns.
12
+ * Wrap libopenmpt informational function: get instruments.
13
+ * Wrap libopenmpt informational function: get samples.
14
+ * Wrap libopenmpt informational function: subsong names.
15
+ * Wrap libopenmpt informational function: channel names.
16
+ * Wrap libopenmpt informational function: order names.
17
+ * Wrap libopenmpt informational function: pattern names.
18
+ * Wrap libopenmpt informational function: instrument names.
19
+ * Wrap libopenmpt informational function: sample names.
20
+ * Wrap libopenmpt render parameter functions.
21
+ * Add an example script to read mod information.
22
+ * Document the mod-info example in the README.
23
+ * Use File.binread to load module data in the example.
24
+ * Rename the example script to show that it uses the API.
25
+ * Start ruby-like interface to a mod: Module.
26
+ * Add the metadata method to the ruby interface.
27
+ * Add duration to the ruby interface.
28
+ * Add the simple informational calls to the ruby interface.
29
+ * Replicate the 'mod-info' example with the ruby interface.
30
+ * Add notion of a closed mod to the ruby interface.
31
+ * Catch a closed mod and return in the ruby interface.
32
+ * Allow access to the metadata via ruby interface methods.
33
+ * Use the new metadata method mappings in mod-info.
34
+ * Update README with details of the second example script.
35
+ * Add the get_string library call to the ruby interface.
36
+ * Add the extensions library call to the ruby interface.
37
+ * Add the supported_extension? library call to the ruby interface.
38
+ * Move the module probe function to the OpenMPT namespace.
39
+ * Get render param 'gain' in the ruby interface.
40
+ * Set render param 'gain' in the ruby interface.
41
+ * Get render param 'stereo_separation' in the ruby interface.
42
+ * Set render param 'stereo_separation' in the ruby interface.
43
+ * Get render param 'interpolation_filter' in the ruby interface.
44
+ * Set render param 'interpolation_filter' in the ruby interface.
45
+ * Get render param 'volume_ramping' in the ruby interface.
46
+ * Set render param 'volume_ramping' in the ruby interface.
47
+ * Add transient_error? call to the ruby interface.
48
+ * Add error_string call to the ruby interface.
49
+ * Change supported_extensions to return list of Symbols.
50
+ * Add sample_rate instance variable to Module.
51
+ * Add missing tests for a closed module.
52
+ * Add stereo read (short) calls to the ruby interface.
53
+ * Add stereo read (float) calls to the ruby interface.
54
+ * Add a 'mod-2-raw' ruby interface example script.
55
+ * Add a note to the README about finding libopenmpt.
56
+ * Update the README with information about the ruby interface.
57
+
3
58
  ## Version 0.1.0
4
59
 
5
60
  * Add Code of Conduct.
data/README.md CHANGED
@@ -7,6 +7,7 @@ A ruby interface to `libopenmpt` - part of [OpenMPT][mpt-home].
7
7
 
8
8
  See the [libopenmpt homepage][lib-home] for more information.
9
9
 
10
+ [![Gem Version](https://badge.fury.io/rb/ffi-openmpt.svg)](https://badge.fury.io/rb/ffi-openmpt)
10
11
  [![Build Status](https://travis-ci.org/hainesr/ffi-openmpt.svg?branch=master)](https://travis-ci.org/hainesr/ffi-openmpt)
11
12
  [![Maintainability](https://api.codeclimate.com/v1/badges/919bd8b421798dbd2719/maintainability)](https://codeclimate.com/github/hainesr/ffi-openmpt/maintainability)
12
13
  [![Coverage Status](https://coveralls.io/repos/github/hainesr/ffi-openmpt/badge.svg?branch=master)](https://coveralls.io/github/hainesr/ffi-openmpt)
@@ -21,7 +22,7 @@ You must have `libopenmpt` installed. On Ubuntu this is done with:
21
22
  $ sudo apt install libopenmpt0
22
23
  ```
23
24
 
24
- You do not need the `libopenmpt` development libraries to be installed.
25
+ You do not need the `libopenmpt` development libraries to be installed. If this library fails to find `libopenmpt` on your platform it might be due to it being named something slightly different. Please [raise an issue][issues] to let me know.
25
26
 
26
27
  Instructions for installing `libopenmpt` from source are available on the [libopenmpt homepage][lib-home].
27
28
 
@@ -45,13 +46,13 @@ $ gem install ffi-openmpt
45
46
 
46
47
  ### Usage
47
48
 
48
- The library wraps the C `libopenmpt` API directly: methods have the same name and signature as their C counterparts. A more friendly ruby-like interface is in development, which will hide the FFI details as much as possible.
49
+ The library wraps the C `libopenmpt` API directly: methods have the same name and signature as their C counterparts. A more friendly ruby-like interface is also available, which hides the FFI details as much as possible.
49
50
 
50
51
  Not all `libopenmpt` methods are wrapped yet, but enough functionality is supplied to load a module, interogate it and render it to a PCM stream.
51
52
 
52
53
  #### A note on strings returned by `libopenmpt`
53
54
 
54
- `libopenmpt` manages the memory of any strings it returns. This means that you must free up such memory explicitly after you have finished with them. Such strings are returned to ruby as [`FFI::Pointer`][ffi-pointer] objects, so the string value can be copied to a ruby string as follows:
55
+ `libopenmpt` manages the memory of any strings it returns. This means that you must free up such memory explicitly after you have finished with them when using the C API. Such strings are returned to ruby as [`FFI::Pointer`][ffi-pointer] objects, so the string value can be copied to a ruby string as follows:
55
56
 
56
57
  ```ruby
57
58
  include FFI::OpenMPT::API
@@ -61,6 +62,63 @@ openmpt_free_string(ptr)
61
62
  puts str
62
63
  ```
63
64
 
65
+ The ruby interface handles all this for you:
66
+
67
+ ```ruby
68
+ str = FFI::OpenMPT.string(:url)
69
+ puts str
70
+ ```
71
+
72
+ ### Example scripts
73
+
74
+ Scripts in the `examples` directory show how to use both the C and ruby APIs. You will need to make sure that `ffi-openmpt` is on your `RUBYLIB` path, or run the examples with `bundle exec`.
75
+
76
+ #### `mod-info` and `mod-info-api`
77
+
78
+ Display information about a mod file, for example:
79
+
80
+ ```shell
81
+ $ ./mod-info lastsun.mod
82
+
83
+ Ruby OpenMPT (ffi-openmpt) file interrogator.
84
+ ---------------------------------------------
85
+
86
+ Filename...: lastsun.mod
87
+ Size.......: 106k
88
+ Type.......: mod
89
+ Format.....: Generic Amiga / PC MOD file
90
+ Tracker....: Master Soundtracker 1.0
91
+ Title......: the last sun
92
+ Duration...: 3:56.4
93
+ Subsongs...: 1
94
+ Channels...: 4
95
+ Orders.....: 35
96
+ Patterns...: 20
97
+ Intruments.: 0
98
+ Samples....: 15
99
+ ```
100
+
101
+ Both `mod-info` and `mod-info-api` output exactly the same data. `mod-info` uses the ruby interface, and `mod-info-api` uses the mapped `libopenmpt` C API directly.
102
+
103
+ #### `mod-2-raw`
104
+
105
+ Render a mod to a raw stereo PCM file. Use the `--float` switch to generate 32 bit float data, or omit for 16bit int data. For example:
106
+
107
+ ```shell
108
+ $ ./mod-2-raw --float lastsun.mod
109
+
110
+ Ruby OpenMPT (ffi-openmpt) Mod to Raw PCM Converter.
111
+ ----------------------------------------------------
112
+
113
+ Filename...: lastsun.mod
114
+ Size.......: 106k
115
+ Type.......: mod
116
+ Output type: float.raw
117
+ Output size: 88687k
118
+ ```
119
+
120
+ The raw output format is simply a blob of `float` or `short` data. The left and right stereo channels are interleaved. The sample rate used is 48,000Hz.
121
+
64
122
  ### Library versions
65
123
 
66
124
  Until this library reaches version 1.0.0 the API may be subject to breaking changes. When version 1.0.0 is released, then the principles of [semantic versioning][semver] will be applied.
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2018 Robert Haines.
5
+ #
6
+ # Licensed under the BSD License. See LICENCE for details.
7
+
8
+ require 'ffi/openmpt'
9
+
10
+ def get_filename(var)
11
+ if var.nil?
12
+ puts 'Please supply a filename to interrogate.'
13
+ exit(1)
14
+ end
15
+
16
+ var.chomp
17
+ end
18
+
19
+ param = get_filename(ARGV[0])
20
+
21
+ if param == '--float'
22
+ mod_path = get_filename(ARGV[1])
23
+ float = true
24
+ else
25
+ mod_path = param
26
+ float = false
27
+ end
28
+
29
+ # Does this file exist? Is it readable?
30
+ unless ::File.readable?(mod_path)
31
+ puts "'#{mod_path}' does not exist, or is not readable."
32
+ exit(1)
33
+ end
34
+
35
+ # Can libopenmpt open this file?
36
+ unless ::FFI::OpenMPT.probe_file(mod_path)
37
+ puts 'libopenmpt can not open this file. Are you sure it is a mod?'
38
+ exit(1)
39
+ end
40
+
41
+ frames_per_read = 1_024
42
+ if float
43
+ raw_type = 'float.raw'
44
+ buffer = ::FFI::MemoryPointer.new(:float, frames_per_read * 2)
45
+ method = :read_interleaved_float_stereo
46
+ else
47
+ raw_type = 'int16.raw'
48
+ buffer = ::FFI::MemoryPointer.new(:int16, frames_per_read * 2)
49
+ method = :read_interleaved_stereo
50
+ end
51
+
52
+ puts
53
+ puts 'Ruby OpenMPT (ffi-openmpt) Mod to Raw PCM Converter.'
54
+ puts '----------------------------------------------------'
55
+ puts
56
+ puts "Filename...: #{::File.basename(mod_path)}"
57
+ puts "Size.......: #{::File.size(mod_path) / 1_024}k"
58
+
59
+ ::FFI::OpenMPT::Module.open(mod_path) do |mod|
60
+ puts "Type.......: #{mod.type}"
61
+ puts "Output type: #{raw_type}"
62
+
63
+ ::File.open(mod_path + '.' + raw_type, 'wb') do |file|
64
+ loop do
65
+ count = mod.send(method, frames_per_read, buffer)
66
+ break if count == 0
67
+ file.write(buffer.read_bytes(count * buffer.type_size * 2))
68
+ end
69
+ end
70
+ end
71
+
72
+ puts "Output size: #{::File.size(mod_path + '.' + raw_type) / 1_024}k"
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2018 Robert Haines.
5
+ #
6
+ # Licensed under the BSD License. See LICENCE for details.
7
+
8
+ require 'ffi/openmpt'
9
+
10
+ if ARGV[0].nil?
11
+ puts 'Please supply a filename to interrogate.'
12
+ exit(1)
13
+ end
14
+
15
+ mod_path = ARGV[0].chomp
16
+
17
+ # Does this file exist? Is it readable?
18
+ unless ::File.readable?(mod_path)
19
+ puts "'#{mod_path}' does not exist, or is not readable."
20
+ exit(1)
21
+ end
22
+
23
+ mod_file = ::File.basename(mod_path)
24
+ mod_size = ::File.size(mod_path) / 1_024
25
+
26
+ # Can libopenmpt open this file?
27
+ unless ::FFI::OpenMPT.probe_file(mod_path)
28
+ puts 'libopenmpt can not open this file. Are you sure it is a mod?'
29
+ exit(1)
30
+ end
31
+
32
+ ::FFI::OpenMPT::Module.open(mod_path) do |mod|
33
+ duration = mod.duration
34
+ duration_mins = duration.floor / 60
35
+ duration_secs = duration % 60
36
+
37
+ puts
38
+ puts 'Ruby OpenMPT (ffi-openmpt) file interrogator.'
39
+ puts '---------------------------------------------'
40
+ puts
41
+ puts "Filename...: #{mod_file}"
42
+ puts "Size.......: #{mod_size}k"
43
+ puts "Type.......: #{mod.type}"
44
+ puts "Format.....: #{mod.type_long}"
45
+ puts "Tracker....: #{mod.tracker}"
46
+ puts "Title......: #{mod.title}"
47
+ puts "Duration...: #{duration_mins}:#{duration_secs.round(3)}"
48
+ puts "Subsongs...: #{mod.subsongs}"
49
+ puts "Channels...: #{mod.channels}"
50
+ puts "Orders.....: #{mod.orders}"
51
+ puts "Patterns...: #{mod.patterns}"
52
+ puts "Instruments: #{mod.instruments}"
53
+ puts "Samples....: #{mod.samples}"
54
+ end
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright (c) 2018 Robert Haines.
5
+ #
6
+ # Licensed under the BSD License. See LICENCE for details.
7
+
8
+ require 'ffi/openmpt'
9
+
10
+ include ::FFI::OpenMPT::API
11
+
12
+ def check_mod_error(mod)
13
+ error = openmpt_module_error_get_last(mod)
14
+ return if error == OPENMPT_ERROR_OK
15
+
16
+ trans = openmpt_error_is_transient(error)
17
+ message_ptr = openmpt_module_error_get_last_message(mod)
18
+ message = message_ptr.read_string
19
+ openmpt_free_string(message_ptr)
20
+
21
+ if trans == 0
22
+ puts "Error! '#{message}'. Exiting."
23
+ exit(1)
24
+ else
25
+ puts "Warning: '#{message}'. Trying to carry on."
26
+ end
27
+ end
28
+
29
+ if ARGV[0].nil?
30
+ puts 'Please supply a filename to interrogate.'
31
+ exit(1)
32
+ end
33
+
34
+ mod_path = ARGV[0].chomp
35
+
36
+ # Does this file exist? Is it readable?
37
+ unless ::File.readable?(mod_path)
38
+ puts "'#{mod_path}' does not exist, or is not readable."
39
+ exit(1)
40
+ end
41
+
42
+ mod_data = ::File.binread(mod_path)
43
+ mod_file = ::File.basename(mod_path)
44
+ mod_size = mod_data.bytesize / 1_024
45
+
46
+ # Can libopenmpt open this file?
47
+ probe_size = openmpt_probe_file_header_get_recommended_size
48
+ probe_result = openmpt_probe_file_header(
49
+ OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT,
50
+ mod_data.byteslice(0, probe_size),
51
+ probe_size,
52
+ mod_data.bytesize,
53
+ LogSilent, nil, ErrorIgnore, nil, nil, nil
54
+ )
55
+
56
+ unless probe_result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS
57
+ puts 'libopenmpt can not open this file. Are you sure it is a mod?'
58
+ exit(1)
59
+ end
60
+
61
+ puts
62
+ puts 'Ruby OpenMPT (ffi-openmpt) file interrogator.'
63
+ puts '---------------------------------------------'
64
+ puts
65
+ puts "Filename...: #{mod_file}"
66
+ puts "Size.......: #{mod_size}k"
67
+
68
+ # Load the mod.
69
+ mod = openmpt_module_create_from_memory2(
70
+ mod_data,
71
+ mod_data.bytesize,
72
+ LogSilent, nil, ErrorIgnore, nil, nil, nil, nil
73
+ )
74
+ check_mod_error(mod)
75
+
76
+ # Mod metadata.
77
+ [
78
+ ['type', 'Type.......:'],
79
+ ['type_long', 'Format.....:'],
80
+ ['tracker', 'Tracker....:'],
81
+ ['title', 'Title......:']
82
+ ].each do |key, display|
83
+ value = openmpt_module_get_metadata(mod, key)
84
+ puts "#{display} #{value.read_string}"
85
+ openmpt_free_string(value)
86
+ end
87
+
88
+ # Mod duration.
89
+ duration = openmpt_module_get_duration_seconds(mod)
90
+ duration_mins = duration.floor / 60
91
+ duration_secs = duration % 60
92
+ puts "Duration...: #{duration_mins}:#{duration_secs.round(3)}"
93
+
94
+ # Other Mod information.
95
+ [
96
+ [:openmpt_module_get_num_subsongs, 'Subsongs...:'],
97
+ [:openmpt_module_get_num_channels, 'Channels...:'],
98
+ [:openmpt_module_get_num_orders, 'Orders.....:'],
99
+ [:openmpt_module_get_num_patterns, 'Patterns...:'],
100
+ [:openmpt_module_get_num_instruments, 'Intruments.:'],
101
+ [:openmpt_module_get_num_samples, 'Samples....:']
102
+ ].each do |func, display|
103
+ value = send(func, mod)
104
+ puts "#{display} #{value}"
105
+ end
@@ -14,3 +14,4 @@ end
14
14
  require 'ffi/openmpt/version'
15
15
  require 'ffi/openmpt/api'
16
16
  require 'ffi/openmpt/openmpt'
17
+ require 'ffi/openmpt/module'
@@ -76,17 +76,69 @@ module FFI
76
76
  :pointer
77
77
  attach_function :openmpt_module_destroy, [:pointer], :void
78
78
 
79
- # Informational/error module calls
79
+ # Informational module calls
80
80
  attach_function :openmpt_module_get_duration_seconds, [:pointer], :double
81
81
  attach_function :openmpt_module_get_metadata_keys, [:pointer], :pointer
82
82
  attach_function :openmpt_module_get_metadata,
83
83
  [:pointer, :string], :pointer
84
+ attach_function :openmpt_module_get_num_subsongs, [:pointer], :int
85
+ attach_function :openmpt_module_get_subsong_name,
86
+ [:pointer, :int], :pointer
87
+ attach_function :openmpt_module_get_num_channels, [:pointer], :int
88
+ attach_function :openmpt_module_get_channel_name,
89
+ [:pointer, :int], :pointer
90
+ attach_function :openmpt_module_get_num_orders, [:pointer], :int
91
+ attach_function :openmpt_module_get_order_name,
92
+ [:pointer, :int], :pointer
93
+ attach_function :openmpt_module_get_num_patterns, [:pointer], :int
94
+ attach_function :openmpt_module_get_pattern_name,
95
+ [:pointer, :int], :pointer
96
+ attach_function :openmpt_module_get_num_instruments, [:pointer], :int
97
+ attach_function :openmpt_module_get_instrument_name,
98
+ [:pointer, :int], :pointer
99
+ attach_function :openmpt_module_get_num_samples, [:pointer], :int
100
+ attach_function :openmpt_module_get_sample_name,
101
+ [:pointer, :int], :pointer
102
+
103
+ # Error module calls
84
104
  attach_function :openmpt_module_error_get_last, [:pointer], :int
85
105
  attach_function :openmpt_module_error_set_last, [:pointer, :int], :void
86
106
  attach_function :openmpt_module_error_get_last_message,
87
107
  [:pointer], :pointer
88
108
  attach_function :openmpt_module_error_clear, [:pointer], :void
89
109
 
110
+ # Probe module calls
111
+ OPENMPT_PROBE_FILE_HEADER_FLAGS_NONE = 0x0
112
+ OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES = 0x1
113
+ OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS = 0x2
114
+ OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT =
115
+ (OPENMPT_PROBE_FILE_HEADER_FLAGS_MODULES |
116
+ OPENMPT_PROBE_FILE_HEADER_FLAGS_CONTAINERS)
117
+
118
+ OPENMPT_PROBE_FILE_HEADER_RESULT_FAILURE = 0
119
+ OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS = 1
120
+ OPENMPT_PROBE_FILE_HEADER_RESULT_WANTMOREDATA = -1
121
+ OPENMPT_PROBE_FILE_HEADER_RESULT_ERROR = -255
122
+
123
+ attach_function :openmpt_probe_file_header_get_recommended_size, [], :int
124
+ attach_function :openmpt_probe_file_header,
125
+ [
126
+ :uint, :pointer, :int, :uint, :pointer, :pointer,
127
+ :pointer, :pointer, :pointer, :pointer
128
+ ],
129
+ :int
130
+
131
+ # Render param module calls
132
+ OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL = 1
133
+ OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT = 2
134
+ OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH = 3
135
+ OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH = 4
136
+
137
+ attach_function :openmpt_module_get_render_param,
138
+ [:pointer, :int, :pointer], :int
139
+ attach_function :openmpt_module_set_render_param,
140
+ [:pointer, :int, :int], :int
141
+
90
142
  # Read module calls
91
143
  attach_function :openmpt_module_read_stereo,
92
144
  [:pointer, :int, :int, :pointer, :pointer],
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018 Robert Haines.
4
+ #
5
+ # Licensed under the BSD License. See LICENCE for details.
6
+
7
+ module FFI
8
+ module OpenMPT
9
+ class Module
10
+
11
+ include API
12
+
13
+ METADATA_KEYS = [
14
+ :type,
15
+ :type_long,
16
+ :container,
17
+ :container_long,
18
+ :tracker,
19
+ :artist,
20
+ :title,
21
+ :date,
22
+ :message,
23
+ :message_raw,
24
+ :warnings
25
+ ].freeze
26
+
27
+ attr_reader :sample_rate
28
+
29
+ def initialize(filename, sample_rate = 48_000)
30
+ @closed = false
31
+ @mod = read_mod(filename)
32
+ @sample_rate = sample_rate
33
+
34
+ # Allocate a reusable single int buffer.
35
+ # This for use by the 'get_render_params'-type calls.
36
+ @int_value = ::FFI::MemoryPointer.new(:int, 1)
37
+ end
38
+
39
+ def self.open(filename)
40
+ m = new(filename)
41
+
42
+ if block_given?
43
+ begin
44
+ yield m
45
+ ensure
46
+ m.close
47
+ end
48
+ end
49
+
50
+ m
51
+ end
52
+
53
+ def sample_rate=(rate)
54
+ @sample_rate = rate if (8_000..192_000).cover?(rate)
55
+ end
56
+
57
+ def duration
58
+ return if closed?
59
+ openmpt_module_get_duration_seconds(@mod)
60
+ end
61
+
62
+ def subsongs
63
+ return if closed?
64
+ openmpt_module_get_num_subsongs(@mod)
65
+ end
66
+
67
+ def channels
68
+ return if closed?
69
+ openmpt_module_get_num_channels(@mod)
70
+ end
71
+
72
+ def orders
73
+ return if closed?
74
+ openmpt_module_get_num_orders(@mod)
75
+ end
76
+
77
+ def patterns
78
+ return if closed?
79
+ openmpt_module_get_num_patterns(@mod)
80
+ end
81
+
82
+ def instruments
83
+ return if closed?
84
+ openmpt_module_get_num_instruments(@mod)
85
+ end
86
+
87
+ def samples
88
+ return if closed?
89
+ openmpt_module_get_num_samples(@mod)
90
+ end
91
+
92
+ def metadata(key)
93
+ return if closed? || !METADATA_KEYS.include?(key)
94
+ get_openmpt_string(:openmpt_module_get_metadata, key.to_s)
95
+ end
96
+
97
+ def gain
98
+ success = openmpt_module_get_render_param(
99
+ @mod, OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL, @int_value
100
+ )
101
+
102
+ success == 1 ? @int_value.read_int : nil
103
+ end
104
+
105
+ def gain=(value)
106
+ openmpt_module_set_render_param(
107
+ @mod, OPENMPT_MODULE_RENDER_MASTERGAIN_MILLIBEL, value
108
+ )
109
+ end
110
+
111
+ def stereo_separation
112
+ success = openmpt_module_get_render_param(
113
+ @mod, OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT, @int_value
114
+ )
115
+
116
+ success == 1 ? @int_value.read_int : nil
117
+ end
118
+
119
+ def stereo_separation=(value)
120
+ openmpt_module_set_render_param(
121
+ @mod, OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT, value
122
+ )
123
+ end
124
+
125
+ def interpolation_filter
126
+ success = openmpt_module_get_render_param(
127
+ @mod, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, @int_value
128
+ )
129
+
130
+ success == 1 ? @int_value.read_int : nil
131
+ end
132
+
133
+ def interpolation_filter=(value)
134
+ openmpt_module_set_render_param(
135
+ @mod, OPENMPT_MODULE_RENDER_INTERPOLATIONFILTER_LENGTH, value
136
+ )
137
+ end
138
+
139
+ def volume_ramping
140
+ success = openmpt_module_get_render_param(
141
+ @mod, OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH, @int_value
142
+ )
143
+
144
+ success == 1 ? @int_value.read_int : nil
145
+ end
146
+
147
+ def volume_ramping=(value)
148
+ openmpt_module_set_render_param(
149
+ @mod, OPENMPT_MODULE_RENDER_VOLUMERAMPING_STRENGTH, value
150
+ )
151
+ end
152
+
153
+ def read_stereo(frames, left, right)
154
+ openmpt_module_read_stereo(@mod, @sample_rate, frames, left, right)
155
+ end
156
+
157
+ def read_interleaved_stereo(frames, buffer)
158
+ openmpt_module_read_interleaved_stereo(
159
+ @mod, @sample_rate, frames, buffer
160
+ )
161
+ end
162
+
163
+ def read_float_stereo(frames, left, right)
164
+ openmpt_module_read_float_stereo(
165
+ @mod, @sample_rate, frames, left, right
166
+ )
167
+ end
168
+
169
+ def read_interleaved_float_stereo(frames, buffer)
170
+ openmpt_module_read_interleaved_float_stereo(
171
+ @mod, @sample_rate, frames, buffer
172
+ )
173
+ end
174
+
175
+ def close
176
+ return if closed?
177
+ @closed = true
178
+ openmpt_module_destroy(@mod)
179
+ end
180
+
181
+ def closed?
182
+ @closed
183
+ end
184
+
185
+ def method_missing(name, *args)
186
+ respond_to?(name) ? metadata(name) : super
187
+ end
188
+
189
+ def respond_to_missing?(name, *all)
190
+ METADATA_KEYS.include?(name) || super
191
+ end
192
+
193
+ private
194
+
195
+ def read_mod(filename)
196
+ data = ::File.binread(filename)
197
+ mod = openmpt_module_create_from_memory2(
198
+ data,
199
+ data.bytesize,
200
+ LogSilent, nil, ErrorIgnore, nil, nil, nil, nil
201
+ )
202
+
203
+ @closed = (mod.address == 0)
204
+ mod
205
+ end
206
+
207
+ def get_openmpt_string(method, *args)
208
+ ptr = send(method, @mod, *args)
209
+ str = ptr.read_string
210
+ openmpt_free_string(ptr)
211
+
212
+ str
213
+ end
214
+ end
215
+ end
216
+ end
@@ -14,5 +14,53 @@ module FFI
14
14
  def self.core_version
15
15
  [API.openmpt_get_core_version].pack('L>').unpack('CCCC')
16
16
  end
17
+
18
+ def self.string(key)
19
+ ptr = API.openmpt_get_string(key.to_s)
20
+ str = ptr.read_string
21
+ API.openmpt_free_string(ptr)
22
+
23
+ str
24
+ end
25
+
26
+ def self.supported_extensions
27
+ ptr = API.openmpt_get_supported_extensions
28
+ exts = ptr.read_string.split(';').map(&:to_sym)
29
+ API.openmpt_free_string(ptr)
30
+
31
+ exts
32
+ end
33
+
34
+ def self.extension_supported?(ext)
35
+ supported = API.openmpt_is_extension_supported(ext.to_s)
36
+ supported == 1
37
+ end
38
+
39
+ def self.transient_error?(error)
40
+ API.openmpt_error_is_transient(error) == 1
41
+ end
42
+
43
+ def self.error_string(error)
44
+ ptr = API.openmpt_error_string(error)
45
+ str = ptr.read_string
46
+ API.openmpt_free_string(ptr)
47
+
48
+ str
49
+ end
50
+
51
+ def self.probe_file(filename)
52
+ probe_size = API.openmpt_probe_file_header_get_recommended_size
53
+ data = ::File.binread(filename, probe_size)
54
+ data_size = ::File.size(filename)
55
+ probe_result = API.openmpt_probe_file_header(
56
+ API::OPENMPT_PROBE_FILE_HEADER_FLAGS_DEFAULT,
57
+ data,
58
+ data.bytesize,
59
+ data_size,
60
+ API::LogSilent, nil, API::ErrorIgnore, nil, nil, nil
61
+ )
62
+
63
+ probe_result == API::OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS
64
+ end
17
65
  end
18
66
  end
@@ -6,6 +6,6 @@
6
6
 
7
7
  module FFI
8
8
  module OpenMPT
9
- VERSION = '0.1.0'
9
+ VERSION = '0.2.0'
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-openmpt
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
  - Robert Haines
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-25 00:00:00.000000000 Z
11
+ date: 2018-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -117,9 +117,13 @@ files:
117
117
  - Rakefile
118
118
  - bin/console
119
119
  - bin/setup
120
+ - examples/mod-2-raw
121
+ - examples/mod-info
122
+ - examples/mod-info-api
120
123
  - ffi-openmpt.gemspec
121
124
  - lib/ffi/openmpt.rb
122
125
  - lib/ffi/openmpt/api.rb
126
+ - lib/ffi/openmpt/module.rb
123
127
  - lib/ffi/openmpt/openmpt.rb
124
128
  - lib/ffi/openmpt/version.rb
125
129
  homepage: https://github.com/hainesr/ffi-openmpt