-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Matt Allan
committed
Dec 26, 2018
0 parents
commit aa8601c
Showing
10 changed files
with
583 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
com.matta.exs24.xrnx |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# EXS24 For Renoise | ||
|
||
This is a renoise tool that adds support for the Logic EXS24 sample format. | ||
|
||
## Installation | ||
|
||
## Usage | ||
|
||
Once the tool is installed .exs instruments should be visible in the sidebar. Clicking on the instrument will load it just like a native renoise instrument. | ||
|
||
The tool will attempt to load the samples automatically. If the sample path cannot be determined you will be prompted to select the folder. | ||
|
||
This tool can currently map the following properties for zones and samples: | ||
|
||
- name | ||
- fine tune | ||
- panning | ||
- oneshot | ||
- base note | ||
- note range | ||
- velocity range | ||
- loop start | ||
- loop end | ||
- loop reverse | ||
|
||
## Packaging | ||
|
||
``` | ||
$ zip -vr com.matta.exs24.xrnx * -x@./exclude.list | ||
``` |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.svn* | ||
*.DS_Store* | ||
*.git* | ||
test* | ||
exclude.list |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
require "util/binary" | ||
require "util/str" | ||
|
||
function load_exs(path) | ||
local fh = io.open(path, "rb") | ||
if fh == nil then | ||
return false | ||
end | ||
|
||
fh:seek("set", 16) | ||
local magic = fh:read(4) | ||
|
||
if magic ~= "SOBT" and magic ~= "SOBJ" and magic ~= "TBOS" and magic ~= "JBOS" then | ||
return false | ||
end | ||
|
||
local big_endian = false | ||
if magic == "SOBT" or magic == "SOBJ" then | ||
big_endian = true | ||
end | ||
|
||
local is_size_expanded = false | ||
fh:seek("set", 4) | ||
local header_size = read_dword(fh, big_endian) | ||
if header_size > 0x8000 then | ||
is_size_expanded = true | ||
end | ||
|
||
local exs = {zones = {}, samples = {}} | ||
local i = 0 | ||
local data_size = fh:seek("end") | ||
|
||
while (i + 84 < data_size) do | ||
fh:seek("set", i) | ||
local sig = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 4) | ||
local size = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 16) | ||
local magic = fh:read(4) | ||
|
||
if is_size_expanded and size > 0x8000 then | ||
size = size - 0x8000 | ||
end | ||
|
||
local chunk_type = bit.rshift(bit.band(sig, 0x0F000000), 24) | ||
|
||
if chunk_type == 0x01 then | ||
if size < 104 then | ||
return false | ||
end | ||
table.insert(exs.zones, create_zone(fh, i, size + 84, big_endian)) | ||
elseif chunk_type == 0x03 then | ||
if size ~= 336 and size ~= 592 then | ||
return false | ||
end | ||
table.insert(exs.samples, create_sample(fh, i, size + 84, big_endian)) | ||
end | ||
i = i + size + 84 | ||
end | ||
|
||
return exs | ||
end | ||
|
||
function create_zone(fh, i, size, big_endian) | ||
local zone = {} | ||
|
||
fh:seek("set", i + 8) | ||
zone.id = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 20) | ||
zone.name = rtrim(fh:read(64)) | ||
|
||
fh:seek("set", i + 84) | ||
local zone_opts = string.byte(fh:read(1)) | ||
zone.pitch = bit.band(zone_opts, bit.lshift(1, 1)) == 0 | ||
zone.oneshot = bit.band(zone_opts, bit.lshift(1, 0)) ~= 0 | ||
zone.reverse = bit.band(zone_opts, bit.lshift(1, 2)) ~= 0 | ||
|
||
fh:seek("set", i + 85) | ||
zone.key = string.byte(fh:read(1)) | ||
|
||
fh:seek("set", i + 86) | ||
zone.fine_tuning = twos_complement(string.byte(fh:read(1)), 8) | ||
|
||
fh:seek("set", i + 87) | ||
zone.pan = twos_complement(string.byte(fh:read(1)), 8) | ||
|
||
fh:seek("set", i + 88) | ||
zone.volume = twos_complement(string.byte(fh:read(1)), 8) | ||
fh:seek("set", i + 164) | ||
zone.coarse_tuning = twos_complement(string.byte(fh:read(1)), 8) | ||
|
||
fh:seek("set", i + 90) | ||
zone.key_low = string.byte(fh:read(1)) | ||
|
||
fh:seek("set", i + 91) | ||
zone.key_high = string.byte(fh:read(1)) | ||
|
||
zone.velocity_range_on = bit.band(zone_opts, bit.lshift(1, 3)) ~= 0 | ||
|
||
fh:seek("set", i + 93) | ||
zone.velocity_low = string.byte(fh:read(1)) | ||
|
||
fh:seek("set", i + 94) | ||
zone.velocity_high = string.byte(fh:read(1)) | ||
|
||
fh:seek("set", i + 96) | ||
zone.sample_start = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 100) | ||
zone.sample_end = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 104) | ||
zone.loop_start = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 108) | ||
zone.loop_end = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 112) | ||
zone.loop_crossfade = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 117) | ||
local loop_opts = string.byte(fh:read(1)) | ||
zone.loop_on = bit.band(loop_opts, bit.lshift(1, 0)) ~= 0 | ||
zone.loop_equal_power = bit.band(loop_opts, bit.lshift(1, 1)) ~= 0 | ||
|
||
if bit.band(zone_opts, bit.lshift(1, 6)) == 0 then | ||
zone.output = -1 | ||
else | ||
fh:seek("set", i + 166) | ||
zone.output = string.byte(fh:read(1)) | ||
end | ||
|
||
fh:seek("set", i + 172) | ||
zone.group_index = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 176) | ||
zone.sample_index = read_dword(fh, big_endian) | ||
|
||
zone.sample_fade = 0 | ||
if size > 188 then | ||
fh:seek("set", i + 188) | ||
zone.sample_fade = read_dword(fh, big_endian) | ||
end | ||
|
||
zone.offset = 0 | ||
if size > 192 then | ||
fh:seek("set", i + 192) | ||
zone.offset = read_dword(fh, big_endian) | ||
end | ||
return zone | ||
end | ||
|
||
function create_sample(fh, i, size, big_endian) | ||
local sample = {} | ||
|
||
fh:seek("set", i + 8) | ||
sample.id = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 20) | ||
sample.name = rtrim(fh:read(64)) | ||
|
||
fh:seek("set", i + 88) | ||
sample.length = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 92) | ||
sample.sample_rate = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 96) | ||
sample.bit_depth = string.byte(fh:read(1)) | ||
|
||
fh:seek("set", i + 112) | ||
sample.type = read_dword(fh, big_endian) | ||
|
||
fh:seek("set", i + 164) | ||
sample.file_path = rtrim(fh:read(256)) | ||
|
||
if size > 420 then | ||
fh:seek("set", i + 420) | ||
sample.file_name = rtrim(fh:read(256)) | ||
else | ||
fh:seek("set", i + 20) | ||
sample.file_name = rtrim(fh:read(64)) | ||
end | ||
|
||
return sample | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
require "exs24" | ||
require "util/process_slicer" | ||
|
||
local function sample_path(instrument_path, instrument_filename, sample_filename) | ||
-- Samples in the current path | ||
if io.exists(instrument_path .. sample_filename) then | ||
return instrument_path | ||
end | ||
|
||
-- GarageBand instrument | ||
-- i.e. "Sampler/Sampler Instruments/Puremagnetik/Eight Bit/" -> | ||
-- "Sampler/Sampler Files/Puremagnetik Samples/Eight Bit" | ||
if instrument_path:find("Sampler Instruments") then | ||
local garageband_path = instrument_path:gsub( | ||
"(%w+)/Sampler Instruments/(%w+)/(%w+)", | ||
"%1/Sampler Files/%2 Samples/%3" | ||
) | ||
if io.exists(garageband_path .. sample_filename) then | ||
return garageband_path | ||
end | ||
end | ||
|
||
-- Logic instrument | ||
-- i.e. "Logic/Sampler Instruments/Puremagnetik/alphaSynth" -> | ||
-- "GarageBand/Instrument Library/Sampler/Sampler Files/Puremagnetik Samples/alphaSynth" | ||
if instrument_path:find("Logic") then | ||
local logic_path = instrument_path:gsub( | ||
"(%w*)Logic/Sampler Instruments/(%w+)/(%w+)", | ||
"%1/GarageBand/Instrument Library/Sampler/Sampler Files/%2 Samples/%3" | ||
) | ||
if io.exists(logic_path .. sample_filename) then | ||
return logic_path | ||
end | ||
end | ||
|
||
-- Sample From Mars | ||
-- i.e. "DX100 From Mars/Logic EXS/DX100 From Mars/Leads/Box Cello.exs" -> | ||
-- "DX100 From Mars/WAV/Box Cello" | ||
if instrument_path:find("Logic EXS") then | ||
local mars_path = instrument_path:gsub( | ||
"(%w+)/Logic EXS/.+", | ||
"%1/WAV/" | ||
) .. instrument_filename:gsub("(.+)\.exs", "%1/") | ||
if io.exists(mars_path .. sample_filename) then | ||
return mars_path | ||
end | ||
end | ||
|
||
return renoise.app():prompt_for_path("Sample files for " .. instrument_filename .. ":") | ||
end | ||
|
||
local function import_samples(instrument, exs, sample_path) | ||
local missing_samples = 0 | ||
|
||
for k,zone in pairs(exs.zones) do | ||
if exs.samples[zone.sample_index + 1] then | ||
local exs_sample = exs.samples[zone.sample_index + 1] | ||
|
||
if io.exists(sample_path .. exs_sample.file_name) then | ||
local sample = instrument:insert_sample_at(#instrument.samples + 1) | ||
if sample.sample_buffer:load_from(sample_path .. exs_sample.file_name) == true then | ||
sample.name = exs_sample.name | ||
-- todo: volume must be 0 - 4, what range is the exs using? | ||
-- exs is using a twos complement byte so it has to be within -128 -127? | ||
sample.volume = 1 | ||
sample.fine_tune = math.max(math.min(zone.fine_tuning, 127), -127) | ||
sample.panning = math.max(math.min((zone.pan / 200) + .5, 1.0), 0.0) | ||
sample.oneshot = zone.oneshot | ||
sample.sample_mapping.base_note = math.max(math.min(zone.key, 119), 0) | ||
sample.sample_mapping.note_range = { | ||
math.max(math.min(zone.key_low, 119), 0), | ||
math.min(119, zone.key_high) | ||
} | ||
sample.sample_mapping.velocity_range = {zone.velocity_low, zone.velocity_high} | ||
sample.loop_start = zone.loop_start + 1 | ||
sample.loop_end = zone.loop_end - 1 | ||
if zone.reverse then | ||
sample.loop_mode = sample.LOOP_MODE_REVERSE | ||
end | ||
end | ||
else | ||
missing_samples = missing_samples + 1 | ||
end | ||
else | ||
missing_samples = missing_samples + 1 | ||
end | ||
renoise.app():show_status(string.format("Importing EXS24 instrument (%d%%)...",((k/#exs.zones))*100)) | ||
coroutine.yield() | ||
end | ||
|
||
renoise.app():show_status("Importing Logic EXS24 instrument complete") | ||
if missing_samples ~= 0 then | ||
renoise.app():show_warning(string.format("%d samples could not be found", missing_samples)) | ||
end | ||
end | ||
|
||
local function import_exs(path) | ||
renoise.app():show_status("Importing EXS24 instrument...") | ||
|
||
local exs = load_exs(path) | ||
|
||
if exs == false then | ||
renoise.app():show_error("The EXS24 instrument could not be loaded") | ||
table.clear(exs) | ||
return false | ||
end | ||
|
||
if #exs.zones == 0 then | ||
renoise.app():show_status("The EXS24 instrument did not contain any zones") | ||
table.clear(exs) | ||
return true | ||
end | ||
|
||
if #exs.samples == 0 then | ||
renoise.app():show_status("The EXS24 instrument did not contain any samples") | ||
table.clear(exs) | ||
return true | ||
end | ||
|
||
local last_slash_pos = path:match"^.*()/" | ||
local instrument_filename = path:sub(last_slash_pos + 1) | ||
local instrument_path = path:sub(1, last_slash_pos) | ||
|
||
local instrument = renoise.song().selected_instrument | ||
instrument:clear() | ||
instrument.name = instrument_filename:sub(1, -5) | ||
|
||
local sample_path = sample_path(instrument_path, instrument_filename, exs.samples[1].file_name) | ||
|
||
if not sample_path then | ||
renoise.app():show_error("The EXS24 sample path could not be found") | ||
end | ||
|
||
local process = ProcessSlicer(import_samples, instrument, exs, sample_path) | ||
process:start() | ||
|
||
return true | ||
end | ||
|
||
if renoise.tool():has_file_import_hook("instrument", {"exs"}) == false then | ||
renoise.tool():add_file_import_hook({ | ||
category = "instrument", | ||
extensions = {"exs"}, | ||
invoke = import_exs | ||
}) | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<RenoiseScriptingTool doc_version="0"> | ||
<ApiVersion>5</ApiVersion> | ||
<Id>com.matta.exs24</Id> | ||
<Version>0.1</Version> | ||
<Author>Matt Allan [[email protected]]</Author> | ||
<Name>EXS24</Name> | ||
<Category>Instruments</Category> | ||
<Description>A tool that allows using EXS24 Instruments</Description> | ||
<Homepage>http://tools.renoise.com/tools/exs24</Homepage> | ||
</RenoiseScriptingTool> |
Oops, something went wrong.