-
Notifications
You must be signed in to change notification settings - Fork 21
/
e2s_sample_import.py
235 lines (199 loc) · 8.86 KB
/
e2s_sample_import.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
"""
Copyright (C) 2017 Jonathan Taquet
This file is part of Oe2sSLE (Open e2sSample.all Library Editor).
Oe2sSLE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Oe2sSLE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Oe2sSLE. If not, see <http://www.gnu.org/licenses/>
"""
import copy
import math
import os
import RIFF
import e2s_sample_all as e2s
import wav_tools
class ImportOptions:
"""e2sSample import options"""
# Sample import number to put in esli.OSC_importNum are starting at ~550
# for user samples. The esli.OSC_importNum value will be overwritten
# with e2s_sample_all.factory_importNums when e2sSample.all is saved
# if the esli.OSC_0index corresponds to a factory sample number.
import_num = 550
def __init__(self):
# default values
self.osc_cat = 'User'
self.loop_type = 0
self.plus_12_db = 0
# force to use the default values if already defined?
self.force_osc_cat = 0
self.force_loop_type = 0
self.force_plus_12_db = 0
# A free sample number will be searched from smp_num_from value
# when a new sample will be imported
self.smp_num_from = 19
# force to import stereo samples as mono
self.force_mono = 0
# the mix value used when samples are converted to mono
# (-1.0: Left, 0.0: Center, 1.0: Right)
self.mono_mix = 0.0
class FromWavError(Exception):
"""Base class for from_wav exceptions."""
pass
class NotWaveFormatPcm(FromWavError):
"""WAV format must be WAVE_FORMAT_PCM."""
pass
class EmptyWav(FromWavError):
"""No data: empty WAV samples are not allowed by e2sSample format."""
pass
class NotSupportedBitPerSample(FromWavError):
"""WAV bit per sample value is not supported"""
pass
def from_wav(filename, import_opts=ImportOptions()):
converted_from = None
with open(filename, 'rb') as f:
sample = e2s.e2s_sample(f)
# check format
fmt = sample.get_fmt()
if fmt.formatTag != fmt.WAVE_FORMAT_PCM:
raise NotWaveFormatPcm
# electribe and Oe2sSLE do not allow empty samples
if not len(sample.get_data()):
raise EmptyWav
if fmt.bitPerSample != 16:
if fmt.bitPerSample == 8:
wav_tools.wav_pcm_8b_to_16b(sample)
converted_from = 8
elif fmt.bitPerSample == 24:
wav_tools.wav_pcm_24b_to_16b(sample)
converted_from = 24
else:
raise NotSupportedBitPerSample
fmt = sample.get_fmt()
if not sample.RIFF.chunkList.get_chunk(b'korg'):
korg_data = e2s.RIFF_korg()
korg_chunk = RIFF.Chunk(header=RIFF.ChunkHeader(id=b'korg'), data=korg_data)
sample.RIFF.chunkList.chunks.append(korg_chunk)
sample.header.size += len(korg_chunk)
korg_chunk = sample.RIFF.chunkList.get_chunk(b'korg')
esli_chunk = korg_chunk.data.chunkList.get_chunk(b'esli')
if not esli_chunk:
esli = e2s.RIFF_korg_esli()
esli_chunk = RIFF.Chunk(header=RIFF.ChunkHeader(id=b'esli'), data=esli)
korg_chunk.data.chunkList.chunks.append(esli_chunk)
esli.OSC_name = bytes(os.path.splitext(os.path.basename(filename))[0], 'ascii', 'ignore')
# todo funtion for that:
data = sample.get_data()
esli.samplingFreq = fmt.samplesPerSec
esli.OSC_EndPoint_offset = esli.OSC_LoopStartPoint_offset = len(data) - fmt.blockAlign
esli.WAV_dataSize = len(data)
if fmt.blockAlign == 4:
# stereo
esli.useChan1 = True
# by default use maximum volume
# unlike electribe which normalizes volume
esli.playVolume = 65535
# use options defaults
esli.OSC_category = esli.OSC_category = e2s.esli_str_to_OSC_cat[import_opts.osc_cat]
esli.playLevel12dB = import_opts.plus_12_db
esli.OSC_importNum = ImportOptions.import_num
ImportOptions.import_num += 1
# by default play speed is same as indicated by Frequency
esli.playLogPeriod = 65535 if fmt.samplesPerSec == 0 else max(0, int(round(63132-math.log2(fmt.samplesPerSec)*3072)))
esli_chunk.header.size += len(esli_chunk)
sample.header.size += len(esli_chunk)
# check if smpl chunk is used
smpl_chunk = sample.RIFF.chunkList.get_chunk(b'smpl')
smpl_used = False
if smpl_chunk:
# use it to initialize loop point
if smpl_chunk.data.numSampleLoops > 0:
# todo: if several LoopData, propose to generate several wavs ?
smpl_loop = smpl_chunk.data.loops[0]
if smpl_loop.playCount != 1:
# looping sample
start = smpl_loop.start*fmt.blockAlign
end = smpl_loop.end*fmt.blockAlign
if start < end and end <= len(data) - fmt.blockAlign:
esli.OSC_LoopStartPoint_offset = start - esli.OSC_StartPoint_address
esli.OSC_OneShot = 0
esli.OSC_EndPoint_offset = end - esli.OSC_StartPoint_address
smpl_used = True
if not smpl_used and import_opts.loop_type == 1:
# loop all
esli.OSC_LoopStartPoint_offset = 0
esli.OSC_OneShot = 0
# check if cue chunk is used
cue_chunk = sample.RIFF.chunkList.get_chunk(b'cue ')
if cue_chunk:
num_samples = len(data) // fmt.blockAlign
cue_points = []
for cue_point in cue_chunk.data.cuePoints:
if cue_point.fccChunk != b'data' or cue_point.sampleOffset >= num_samples:
# unhandled cue_point
continue
else:
cue_points.append(cue_point)
start_sample = esli.OSC_StartPoint_address // fmt.blockAlign
num_slices = 0
cue_points.sort(key=lambda cue_point: cue_point.sampleOffset)
for cue_point in cue_points:
if num_slices >= 64:
break
else:
esli.slices[num_slices].start = cue_point.sampleOffset - start_sample
esli.slices[num_slices].length = num_samples - cue_point.sampleOffset
if num_slices > 0:
esli.slices[num_slices-1].length = esli.slices[num_slices].start - esli.slices[num_slices-1].start
num_slices += 1
else:
esli = esli_chunk.data
converted_to_mono = apply_forced_options(sample, import_opts)
return sample, converted_from, converted_to_mono
def apply_forced_options(e2s_sample, import_opts):
converted_to_mono = False
esli = e2s_sample.get_esli()
if import_opts.force_osc_cat:
esli.OSC_category = e2s.esli_str_to_OSC_cat[import_opts.osc_cat]
if import_opts.force_loop_type:
if import_opts.loop_type == 0:
# force one shot
esli.OSC_LoopStartPoint_offset = esli.OSC_EndPoint_offset
esli.OSC_OneShot = 1
elif import_opts.loop_type == 1:
# force loop all
esli.OSC_LoopStartPoint_offset = 0
esli.OSC_OneShot = 0
if import_opts.force_plus_12_db:
esli.playLevel12dB = import_opts.plus_12_db
if import_opts.force_mono:
converted_to_mono = _convert_to_mono(e2s_sample, import_opts.mono_mix)
return converted_to_mono
def _convert_to_mono(e2s_sample, mix):
converted = False
num_chans = e2s_sample.get_fmt().channels
if num_chans > 1:
converted = True
_esli = e2s.RIFF_korg_esli()
_esli.rawdata[:] = e2s_sample.get_esli().rawdata[:]
prev_fmt = e2s_sample.get_fmt()
_fmt = copy.deepcopy(prev_fmt)
_fmt.channels=1
_fmt.avgBytesPerSec = _fmt.avgBytesPerSec // prev_fmt.channels
_fmt.blockAlign = _fmt.blockAlign // prev_fmt.channels
_esli.OSC_StartPoint_address = _esli.OSC_StartPoint_address // prev_fmt.channels
_esli.OSC_LoopStartPoint_offset = _esli.OSC_LoopStartPoint_offset // prev_fmt.channels
_esli.OSC_EndPoint_offset = _esli.OSC_EndPoint_offset // prev_fmt.channels
_esli.WAV_dataSize = _esli.WAV_dataSize // prev_fmt.channels
_esli.useChan1 = False
w = ((1 - mix)/2, 1 - (1 - mix)/2) + (0,)*(num_chans-2)
_data = wav_tools.wav_mchan_to_mono(e2s_sample.get_data().rawdata, w)
e2s_sample.get_esli().rawdata = _esli.rawdata
e2s_sample.get_fmt().__dict__ = _fmt.__dict__
e2s_sample.get_data().rawdata = _data
return converted