Created
November 2, 2019 15:40
-
-
Save damncabbage/a6419afb5044b3690d555114aab3b1d8 to your computer and use it in GitHub Desktop.
Voyager Image, Ruby port
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Source: https://gist.github.com/Papierkorb/019ef4b330923175db4a7e6e54f4fb41 | |
# This code is meant for debugging purposes. Its performance is probably bad, | |
# it's just meant to work for some hacky scripts you throw out tomorrow :) | |
# Create an instance of `Bitmap`, passing the file (IO or string), the width and height. | |
# Then push one (or more) 32bit pixels in ARGB format using `Bitmap#<<`. | |
# Pixel format is thus: 0xAARRGGBB (Alpha is highest byte, blue lowest). | |
# (Alpha is ignored by most programs however) | |
class Bitmap | |
MAGIC = "BM" | |
def initialize(io, width, height) | |
io = File.open(io, "wb") if io.is_a?(String) | |
@io = io | |
@width = width | |
@height = height | |
write_header | |
if block_given? | |
yield self | |
close | |
end | |
end | |
private def write_header | |
bitmap_size = @width * @height * 4 | |
file_size = 54 + bitmap_size | |
# Magic | |
@io.write MAGIC | |
# File size, Reserved, Offset to bitmap: | |
@io.write [ file_size, 0, 54 ].pack("l<*") | |
info = [ | |
40, # u32, biSize | |
@width, # i32, biWidth | |
-@height, # i32, biHeight | |
1, # u16, biPlanes | |
32, # u16, biBitCount | |
0, # u32, biCompression | |
bitmap_size, # u32, biSizeImage | |
0, # i32, biXPelsPerMeter | |
0, # i32, biYPelsPerMeter | |
0, # u32, biClrUsed | |
0, # u32, biClrImportant | |
] | |
@io.write info.pack("L<l<l<S<S<l<l<L<L<l<l<") | |
end | |
def <<(argb) | |
@io.write Array(argb).pack("l<*") | |
self | |
end | |
def close | |
@io.flush | |
@io.close | |
end | |
end | |
# Usage sample: 256x256 bitmap with a color gradient: | |
# Best piped through display: ruby bmp.rb | display - | |
# Or put something else than `STDOUT`. | |
# pic = Bitmap.new(STDOUT, 256, 256) | |
# | |
# 256.times do |y| | |
# c = y << 8 # Use the y as green channel | |
# pic << Array.new(256){|i| c | i} # And x as blue channel. | |
# end | |
# | |
# pic.close # Done. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source 'https://rubygems.org' | |
gem 'wavefile' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'bundler/setup' | |
require 'logger' | |
require 'wavefile' | |
require File.expand_path('../lib/bitmap', File.dirname(__FILE__)) | |
def main | |
init_logger! | |
wav, image = ["./384kHzStereo.wav", "./384kHzStereo.bmp"] | |
wav_to_image(wav, image) | |
info!("Done!") | |
end | |
class Enumerator | |
def skip(n) | |
n.times { self.next } | |
self | |
end | |
def take(n) | |
result = [] | |
n.times { result << self.next } | |
result | |
end | |
end | |
def init_logger! | |
@logger = Logger.new(STDOUT) | |
@logger.level = ENV.fetch('LOG_LEVEL', 'DEBUG') | |
end | |
[:debug, :info, :error].each do |level| | |
define_method("#{level}!") do |message, *args| | |
@logger.send(level, message.gsub(/{(\?:)?}/, '%s') % args.map {|a| a&.inspect }) | |
end | |
end | |
def wav_to_image(wav_filename, img_filename) | |
lines = scaled_lines_from_wav(wav_filename, 0..255) | |
image = image_from_lines(lines, img_filename) | |
end | |
def scaled_lines_from_wav(filename, rescale_range) | |
lines = [] | |
WaveFile::Reader.new(filename) do |reader| | |
# File info from afinfo: | |
# 384kHzStereo.wav | |
# File: 384kHzStereo.wav | |
# File type ID: WAVE | |
# Num Tracks: 1 | |
# ---- | |
# Data format: 2 ch, 384000 Hz, 'lpcm' (0x00000009) 32-bit little-endian float | |
# no channel layout. | |
# estimated duration: 473.856000 sec | |
# audio bytes: 1455685632 | |
# audio packets: 181960704 | |
# bit rate: 24576000 bits per second | |
# packet size upper bound: 8 | |
# maximum packet size: 8 | |
# audio data file offset: 88 | |
# optimized | |
# source bit depth: F32 | |
# ---- | |
# File info from Hound: | |
# println!("{:?}", reader.spec()); | |
# WavSpec { channels: 2, sample_rate: 384000, bits_per_sample: 32, sample_format: Float } | |
# | |
# Extra info: | |
# num_samples = 363_921_408 | |
# start (before first 'buffer'): 00:00:15.674 sec, 6_023_017 samples | |
# length of each 'content' chunk: 0.006 sec, 2564 samples long | |
# length of each 'buffer' chunk: 0.002 sec, 649 sampless long | |
before_first_buffer_chunk = 6_019_836 | |
content_chunk_length = 2_555 | |
buffer_chunk_length = 643 | |
before_last_buffer_chunk = 7_707_417 | |
before_first_buffer_chunk = 6_019_206 | |
content_chunk_length = 2_555 | |
buffer_chunk_length = 643 | |
before_last_buffer_chunk = 7_707_417 | |
picture_length = before_last_buffer_chunk - before_first_buffer_chunk; # 1687581 | |
# TODO: where is 525? | |
discard_buffer = 0; # try ~40 ... or, TODO: try aligning on lowest. | |
#num_samples = reader.sampler_info. | |
# Total samples should be 363_921_408... | |
num_samples = reader.total_sample_frames # => 181_960_704 | |
# ... which is (363_921_408 / 2) | |
# TO DO: | |
# - Seek to where we think the first sample is starting (hardcoded) | |
reader.read(before_first_buffer_chunk) | |
# - Figure out how long we want to grab a sample for (hardcoded) | |
# - Iterate for that amount, pulling out only the Left channel (the one with the calibration picture on it). | |
samples = reader | |
.read(picture_length) | |
.samples | |
.map(&:first) # Left channel only | |
.each # -> Enumerator | |
for _idx in 0...(picture_length / (content_chunk_length + buffer_chunk_length)) | |
line = [] | |
min, max, line = samples | |
.skip(buffer_chunk_length) | |
.take(content_chunk_length) | |
.each_slice(5).map(&:first) | |
.reduce([Float::INFINITY, Float::INFINITY * -1, []]) do |(min, max, ss), sample| | |
# TODO: Not a good idea...? | |
#if sample < 0.0 | |
# sample = 0 | |
#end | |
ss.push(sample) | |
result = [min, max, ss] | |
if sample < min | |
result[0] = sample | |
end | |
if sample > max | |
result[1] = sample | |
end | |
#printf("% 02.6f | % 02.6f .. % 02.6f\n", sample, result[0], result[1]) | |
result | |
end | |
rescaled_line = line | |
.map do |sample| | |
rescale_from, rescale_to = rescale_range.first, rescale_range.last | |
((sample - min) * (rescale_to - rescale_from) / (max - min) + rescale_from).floor | |
end | |
lines.push(rescaled_line) | |
end | |
debug!("Samples: {} {:?} {:?}", num_samples) | |
debug!("Lines: {} {}", lines.class, lines.length) | |
debug!("Lines[0]: {} {}", lines[0].class, lines[0].length) | |
debug!("Lines[0][0]: {} {}", lines[0][0].class, lines[0][0].inspect) | |
end | |
lines | |
end | |
def image_from_lines(lines, filename) | |
# TODO: Actually make things the right size. | |
# 525 high x 2564 wide, ie. picture_length high x content_chunk_length wide | |
#height = 528 | |
#width = 2564 | |
height = lines.length | |
width = lines[0].length | |
#picture = Array.new(0, 1353792) | |
Bitmap.new(filename, width, height) do |bmp| | |
#for row_idx in 0..lines.length | |
# row_idx_base = row_idx * width | |
# v_row = lines[row_idx] | |
# debug!("{}, {}, {}: {}", row_idx, row_idx_base, row_idx_base + v_row.length, v_row.length) | |
# for col_idx in 0..v_row.length | |
# picture[row_idx_base + col_idx] = v_row[col_idx] | |
# end | |
#end | |
for row_idx in 0...(lines.length) | |
bmp << lines[row_idx].map do |rb| | |
b = 255 - rb | |
0xFF000000 | (b << 16) | (b << 8) | b | |
end | |
end | |
end | |
end | |
main | |
###include WaveFile | |
### | |
###STARTING_NUMBER_OF_SECONDS = 3 * 60 # Starting point is 3 minutes into file | |
###ENDING_NUMBER_OF_SECONDS = 7 * 60 # Ending point is 7 minutes into file | |
### | |
###Reader.new("file_to_trim.wav") do |reader| | |
### STARTING_SAMPLE_FRAME = reader.format.sample_rate * STARTING_NUMBER_OF_SECONDS | |
### ENDING_SAMPLE_FRAME = reader.format.sample_rate * ENDING_NUMBER_OF_SECONDS | |
### | |
### # Read up to the starting trim point | |
### throwaway_buffer = reader.read(STARTING_SAMPLE_FRAME) | |
### | |
### Writer.new("trimmed_file.wav", reader.format) do |writer| | |
### buffer = reader.read(ENDING_SAMPLE_FRAME - STARTING_SAMPLE_FRAME) | |
### writer.write(buffer) | |
### end | |
###end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment