lamer 0.1.2 → 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6bccc9f1c692bd735c7aa5b5a05fca525b81c37d9e28cc784aa1e89f04311167
4
+ data.tar.gz: 3523658b60c220c13b4e5414eeb06ec65ed6a0605091f89dbeda85d8c051d4bd
5
+ SHA512:
6
+ metadata.gz: 7b9206a84102cedb93bcd1380d7b2bb101ac04aaca534512bbd3914e89869809bcd7b5ce20feefd79aed5b3e142f54c247c2f7c36b90cf9191d3e28562beaa11
7
+ data.tar.gz: cece25de9d528db0aae546fec498decaf807c64a58f1a242d850a353cf75447b282553e7d101dc3f2524aa062c31680a751442d88aa21532d3da55f34a5fb225
data/LICENSE CHANGED
@@ -1,5 +1,5 @@
1
- Copyright (c) 2010 Chris Anderson, Mauricio Gomes
2
-
1
+ Copyright (c) 2025 Mauricio Gomes
2
+
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
5
5
  "Software"), to deal in the Software without restriction, including
@@ -7,14 +7,14 @@ without limitation the rights to use, copy, modify, merge, publish,
7
7
  distribute, sublicense, and/or sell copies of the Software, and to
8
8
  permit persons to whom the Software is furnished to do so, subject to
9
9
  the following conditions:
10
-
10
+
11
11
  The above copyright notice and this permission notice shall be
12
12
  included in all copies or substantial portions of the Software.
13
-
13
+
14
14
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
15
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
16
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,45 +1,237 @@
1
- Lamer
2
- =====
1
+ # Lamer
3
2
 
4
- ### Ruby Wrapper Around the LAME Library
3
+ Ruby FFI bindings for the LAME MP3 encoder library.
5
4
 
6
- Currently the program wraps around the [LAME](http://lame.sourceforge.net/) executable and exposes the LAME
7
- command line options to your Ruby program.
5
+ Encode audio to MP3 directly from Ruby without shelling out to a command line tool. Supports encoding from WAV files, raw PCM data, or re-encoding existing MP3 files.
8
6
 
9
- Todo
10
- ----
7
+ ## Requirements
11
8
 
12
- * Documentation
13
- * Wrap the LAME library as an extension to remove the binary requirements
9
+ - Ruby 3.2 or later
10
+ - libmp3lame library installed on your system
14
11
 
15
- Install
16
- -------
12
+ ## Installation
17
13
 
18
- sudo gem install lamer
14
+ First, install the LAME library:
19
15
 
20
- Prerequisites
21
- -------------
16
+ **macOS (Homebrew)**
22
17
 
23
- ### LAME
18
+ ```bash
19
+ brew install lame
20
+ ```
24
21
 
25
- [LAME](http://lame.sourceforge.net/) is a high quality MPEG Audio Layer III (MP3) encoder licensed under the LGPL. Below is
26
- a list of pre-built binaries and a link to the source code.
22
+ **Ubuntu/Debian**
27
23
 
28
- * [OS X](http://web.me.com/krmathis/)
29
- * [openSUSE](http://packman.links2linux.org/package/lame)
30
- * [Ubuntu](https://help.ubuntu.com/community/Medibuntu)
31
- * [RHEL/CentOS/Fedora ](http://atrpms.net/name/lame/)
32
- * [Source](http://lame.sourceforge.net/download.php)
24
+ ```bash
25
+ sudo apt-get install libmp3lame-dev
26
+ ```
33
27
 
34
- Credits
35
- -------
28
+ **Fedora/RHEL/CentOS**
36
29
 
37
- This wrapper is built around the code of [lame-encoder](http://lame-encoder.rubyforge.org/) by
38
- Chris Anderson.
30
+ ```bash
31
+ sudo dnf install lame-devel
32
+ ```
39
33
 
40
- Authors
41
- -------
34
+ Then add the gem to your Gemfile:
42
35
 
43
- * [Chris Anderson](http://github.com/jchris)
44
- * [Mauricio Gomes](http://github.com/mgomes)
36
+ ```ruby
37
+ gem 'lamer'
38
+ ```
45
39
 
40
+ Or install it directly:
41
+
42
+ ```bash
43
+ gem install lamer
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ```ruby
49
+ require 'lamer'
50
+
51
+ # One-liner encoding
52
+ Lamer.encode("input.wav", "output.mp3", bitrate: 192, mode: :stereo)
53
+
54
+ # One-liner decoding
55
+ Lamer.decode("input.mp3", "output.wav")
56
+
57
+ # With ID3 tags
58
+ Lamer.encode("input.wav", "output.mp3", bitrate: 192, id3: { title: "My Song", artist: "My Artist" })
59
+
60
+ # Block syntax for more control
61
+ Lamer.encode("input.wav", "output.mp3") do |l|
62
+ l.bitrate(256)
63
+ l.vbr_quality(2)
64
+ l.id3(title: "My Song", artist: "My Artist", album: "My Album")
65
+ end
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ### Basic Encoding
71
+
72
+ ```ruby
73
+ require 'lamer'
74
+
75
+ lamer = Lamer.new
76
+ lamer.bitrate(128)
77
+ lamer.input_file("audio.wav")
78
+ lamer.output_file("audio.mp3")
79
+ lamer.convert!
80
+ ```
81
+
82
+ ### VBR Encoding
83
+
84
+ ```ruby
85
+ lamer = Lamer.new
86
+ lamer.vbr_quality(2) # 0 = best quality, 9 = smallest file
87
+ lamer.input_file("audio.wav")
88
+ lamer.output_file("audio.mp3")
89
+ lamer.convert!
90
+ ```
91
+
92
+ ### Setting Quality and Mode
93
+
94
+ ```ruby
95
+ lamer = Lamer.new
96
+ lamer.bitrate(320)
97
+ lamer.encode_quality(:high) # or 0-9, where 0 is best
98
+ lamer.mode(:stereo) # :mono, :stereo, :joint, :auto
99
+ lamer.input_file("audio.wav")
100
+ lamer.output_file("audio.mp3")
101
+ lamer.convert!
102
+ ```
103
+
104
+ ### ID3 Tags
105
+
106
+ ```ruby
107
+ lamer = Lamer.new
108
+ lamer.bitrate(192)
109
+ lamer.id3(
110
+ title: "Song Title",
111
+ artist: "Artist Name",
112
+ album: "Album Name",
113
+ year: 2024,
114
+ track_number: 1,
115
+ genre: "Rock"
116
+ )
117
+ lamer.input_file("audio.wav")
118
+ lamer.output_file("audio.mp3")
119
+ lamer.convert!
120
+ ```
121
+
122
+ ### Re-encoding MP3 Files
123
+
124
+ ```ruby
125
+ lamer = Lamer.new
126
+ lamer.bitrate(64)
127
+ lamer.mode(:mono)
128
+ lamer.input_file("highquality.mp3")
129
+ lamer.output_file("lowquality.mp3")
130
+ lamer.convert!
131
+ ```
132
+
133
+ ### Decoding MP3 to WAV
134
+
135
+ ```ruby
136
+ lamer = Lamer.new
137
+ lamer.decode_mp3!
138
+ lamer.input_file("audio.mp3")
139
+ lamer.output_file("audio.wav")
140
+ lamer.decode!
141
+ ```
142
+
143
+ ### In-Memory Encoding
144
+
145
+ Encode PCM samples directly without file I/O:
146
+
147
+ ```ruby
148
+ lamer = Lamer.new
149
+ lamer.bitrate(128)
150
+
151
+ # 16-bit signed integer samples
152
+ left_channel = [0, 1000, 2000, 3000, ...]
153
+ right_channel = [0, 1000, 2000, 3000, ...]
154
+
155
+ mp3_data = lamer.encode_buffer(left_channel, right_channel)
156
+ File.binwrite("output.mp3", mp3_data)
157
+ ```
158
+
159
+ ### Float Sample Encoding
160
+
161
+ For audio processing pipelines that use floating point samples:
162
+
163
+ ```ruby
164
+ lamer = Lamer.new
165
+ lamer.bitrate(256)
166
+
167
+ # Float samples in range -1.0 to 1.0
168
+ left_channel = [0.0, 0.5, 1.0, 0.5, ...]
169
+ right_channel = [0.0, 0.5, 1.0, 0.5, ...]
170
+
171
+ mp3_data = lamer.encode_float_buffer(left_channel, right_channel)
172
+ ```
173
+
174
+ ## Available Options
175
+
176
+ ### Bitrate
177
+
178
+ Valid bitrates in kbps: 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320
179
+
180
+ ```ruby
181
+ lamer.bitrate(128)
182
+ ```
183
+
184
+ ### Sample Rate
185
+
186
+ Valid sample rates in kHz: 8, 11.025, 12, 16, 22.05, 24, 32, 44.1, 48
187
+
188
+ ```ruby
189
+ lamer.sample_rate(44.1)
190
+ ```
191
+
192
+ ### VBR Quality
193
+
194
+ Values 0-9, where 0 is highest quality and 9 is smallest file size.
195
+
196
+ ```ruby
197
+ lamer.vbr_quality(2)
198
+ ```
199
+
200
+ ### Encoding Quality
201
+
202
+ Values 0-9, where 0 is slowest/best and 9 is fastest/worst. Also accepts :high (2) and :fast (7).
203
+
204
+ ```ruby
205
+ lamer.encode_quality(:high)
206
+ ```
207
+
208
+ ### Channel Mode
209
+
210
+ Available modes: :mono, :stereo, :joint, :auto, :mid_side
211
+
212
+ ```ruby
213
+ lamer.mode(:joint)
214
+ ```
215
+
216
+ ### Filters
217
+
218
+ Set highpass and lowpass filter frequencies in kHz:
219
+
220
+ ```ruby
221
+ lamer.highpass(0.1) # 100 Hz
222
+ lamer.lowpass(16.0) # 16 kHz
223
+ ```
224
+
225
+ ## Version Information
226
+
227
+ ```ruby
228
+ Lamer.lame_version # Returns the LAME library version string
229
+ ```
230
+
231
+ ## Author
232
+
233
+ [Mauricio Gomes](http://github.com/mgomes)
234
+
235
+ ## License
236
+
237
+ MIT License
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lamer
4
+ class Decoder
5
+ # LAME can return up to 1152 samples per MP3 frame, but when decoding
6
+ # a large chunk it can return many frames worth of samples at once.
7
+ # Using a larger buffer to handle typical MP3 files.
8
+ PCM_BUFFER_SIZE = 1152 * 128
9
+ MP3_READ_SIZE = 16384
10
+
11
+ attr_reader :hip, :mp3data
12
+
13
+ def initialize
14
+ @hip = FFI.hip_decode_init
15
+ raise Error, "Failed to initialize HIP decoder" if @hip.null?
16
+
17
+ @mp3data = FFI::Mp3Data.new
18
+ @initialized = false
19
+
20
+ ObjectSpace.define_finalizer(self, self.class.release(@hip))
21
+ end
22
+
23
+ def self.release(hip)
24
+ proc { FFI.hip_decode_exit(hip) unless hip.null? }
25
+ end
26
+
27
+ def decode_file(input_path, output_path)
28
+ File.open(input_path, "rb") do |input|
29
+ File.open(output_path, "wb") do |output|
30
+ left_buf = ::FFI::MemoryPointer.new(:short, PCM_BUFFER_SIZE)
31
+ right_buf = ::FFI::MemoryPointer.new(:short, PCM_BUFFER_SIZE)
32
+ mp3_buf = ::FFI::MemoryPointer.new(:uchar, MP3_READ_SIZE)
33
+
34
+ first_frame = true
35
+ channels = 2
36
+ sample_rate = 44100
37
+
38
+ until input.eof?
39
+ data = input.read(MP3_READ_SIZE)
40
+ break if data.nil? || data.empty?
41
+
42
+ mp3_buf.put_bytes(0, data)
43
+
44
+ if first_frame
45
+ samples = FFI.hip_decode_headers(@hip, mp3_buf, data.bytesize, left_buf, right_buf, @mp3data)
46
+ if samples > 0 || @mp3data[:header_parsed] != 0
47
+ channels = @mp3data[:stereo]
48
+ sample_rate = @mp3data[:samplerate]
49
+ write_wav_header(output, channels, sample_rate)
50
+ first_frame = false
51
+ end
52
+ else
53
+ samples = FFI.hip_decode(@hip, mp3_buf, data.bytesize, left_buf, right_buf)
54
+ end
55
+
56
+ next unless samples > 0
57
+
58
+ left_samples = left_buf.read_array_of_int16(samples)
59
+
60
+ if channels == 2
61
+ right_samples = right_buf.read_array_of_int16(samples)
62
+ interleaved = left_samples.zip(right_samples).flatten
63
+ output.write(interleaved.pack("s*"))
64
+ else
65
+ output.write(left_samples.pack("s*"))
66
+ end
67
+ end
68
+
69
+ finalize_wav_header(output, channels)
70
+ end
71
+ end
72
+
73
+ true
74
+ end
75
+
76
+ def decode_buffer(mp3_data)
77
+ left_buf = ::FFI::MemoryPointer.new(:short, PCM_BUFFER_SIZE)
78
+ right_buf = ::FFI::MemoryPointer.new(:short, PCM_BUFFER_SIZE)
79
+ mp3_buf = ::FFI::MemoryPointer.new(:uchar, mp3_data.bytesize)
80
+ mp3_buf.put_bytes(0, mp3_data)
81
+
82
+ all_left = []
83
+ all_right = []
84
+
85
+ offset = 0
86
+ remaining = mp3_data.bytesize
87
+
88
+ while remaining > 0
89
+ chunk_size = [remaining, MP3_READ_SIZE].min
90
+ chunk_ptr = mp3_buf + offset
91
+
92
+ samples = if !@initialized
93
+ result = FFI.hip_decode_headers(@hip, chunk_ptr, chunk_size, left_buf, right_buf, @mp3data)
94
+ @initialized = true if result >= 0 || @mp3data[:header_parsed] != 0
95
+ result
96
+ else
97
+ FFI.hip_decode(@hip, chunk_ptr, chunk_size, left_buf, right_buf)
98
+ end
99
+
100
+ if samples > 0
101
+ all_left.concat(left_buf.read_array_of_int16(samples))
102
+ all_right.concat(right_buf.read_array_of_int16(samples)) if stereo?
103
+ end
104
+
105
+ offset += chunk_size
106
+ remaining -= chunk_size
107
+ end
108
+
109
+ {
110
+ left: all_left,
111
+ right: stereo? ? all_right : nil,
112
+ channels: @mp3data[:stereo],
113
+ sample_rate: @mp3data[:samplerate],
114
+ bitrate: @mp3data[:bitrate]
115
+ }
116
+ end
117
+
118
+ def stereo?
119
+ @mp3data[:stereo] == 2
120
+ end
121
+
122
+ def sample_rate
123
+ @mp3data[:samplerate]
124
+ end
125
+
126
+ def bitrate
127
+ @mp3data[:bitrate]
128
+ end
129
+
130
+ private
131
+
132
+ def write_wav_header(file, channels, sample_rate)
133
+ bits_per_sample = 16
134
+ byte_rate = sample_rate * channels * bits_per_sample / 8
135
+ block_align = channels * bits_per_sample / 8
136
+
137
+ file.write("RIFF")
138
+ file.write([0].pack("V")) # placeholder for file size
139
+ file.write("WAVE")
140
+
141
+ file.write("fmt ")
142
+ file.write([16].pack("V"))
143
+ file.write([1].pack("v")) # PCM
144
+ file.write([channels].pack("v"))
145
+ file.write([sample_rate].pack("V"))
146
+ file.write([byte_rate].pack("V"))
147
+ file.write([block_align].pack("v"))
148
+ file.write([bits_per_sample].pack("v"))
149
+
150
+ file.write("data")
151
+ file.write([0].pack("V")) # placeholder for data size
152
+ end
153
+
154
+ def finalize_wav_header(file, channels)
155
+ file_size = file.size
156
+ data_size = file_size - 44
157
+
158
+ file.seek(4)
159
+ file.write([file_size - 8].pack("V"))
160
+
161
+ file.seek(40)
162
+ file.write([data_size].pack("V"))
163
+ end
164
+ end
165
+ end