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 +4 -4
- data/.rubocop.yml +22 -0
- data/CHANGES.md +55 -0
- data/README.md +61 -3
- data/examples/mod-2-raw +72 -0
- data/examples/mod-info +54 -0
- data/examples/mod-info-api +105 -0
- data/lib/ffi/openmpt.rb +1 -0
- data/lib/ffi/openmpt/api.rb +53 -1
- data/lib/ffi/openmpt/module.rb +216 -0
- data/lib/ffi/openmpt/openmpt.rb +48 -0
- data/lib/ffi/openmpt/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06607b1145d777c793ee96ca49e70132f74dd3ec2289957909519a109a64f7d7
|
4
|
+
data.tar.gz: f5ef317907e2ff4866a1d0832ba64b820f409dc03dce32613321bc3767cc5c9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a03b9aa048cfcb588e36b147b396d04653fb5d55780d2f6ff879954ae7d0a783ee414c003477f3b5e3693c930baad0d400c755a428bc5aa2528264f9c7064fb
|
7
|
+
data.tar.gz: bd5d1f18bdaa730e22b38f281d2281c3eb544631a79fedf10dc32bc1f7e897afe3a54da726c83cc7f19398f527fc9810f5038ff9df0a1dd04ec1b86663c8a86c
|
data/.rubocop.yml
CHANGED
@@ -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
|
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.
|
data/examples/mod-2-raw
ADDED
@@ -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"
|
data/examples/mod-info
ADDED
@@ -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
|
data/lib/ffi/openmpt.rb
CHANGED
data/lib/ffi/openmpt/api.rb
CHANGED
@@ -76,17 +76,69 @@ module FFI
|
|
76
76
|
:pointer
|
77
77
|
attach_function :openmpt_module_destroy, [:pointer], :void
|
78
78
|
|
79
|
-
# Informational
|
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
|
data/lib/ffi/openmpt/openmpt.rb
CHANGED
@@ -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
|
data/lib/ffi/openmpt/version.rb
CHANGED
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.
|
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-
|
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
|