Initial attempt at timestretching implementation.
[calfbox.git] / test.py
blob13a7ae3ba40a47350d00bc4346a3f844a9f61112
1 import os
2 import sys
3 import struct
4 import time
5 import unittest
7 # This is for locale testing
8 from gi.repository import GObject, Gdk, Gtk
10 from calfbox import cbox
11 cbox.init_engine("")
12 cbox.start_noaudio(44100)
14 cbox.Config.add_section("drumpattern:pat1", """
15 title=Straight - Verse
16 beats=4
17 track1=bd
18 track2=sd
19 track3=hh
20 track4=ho
21 bd_note=c1
22 sd_note=d1
23 hh_note=f#1
24 ho_note=a#1
25 bd_trigger=9... .... 9.6. ....
26 sd_trigger=.... 9..5 .2.. 9...
27 hh_trigger=9353 7353 7353 73.3
28 ho_trigger=.... .... .... ..3.
29 """)
30 cbox.Config.add_section("fxpreset:piano_reverb", """
31 engine=reverb
32 """)
33 cbox.Config.add_section("instrument:vintage", """
34 engine=sampler
35 """)
37 global Document
38 Document = cbox.Document
40 Document.dump()
42 class TestCbox(unittest.TestCase):
43 def verify_uuid(self, uuid, class_name, path = None):
44 self.assertEqual(cbox.GetThings(Document.uuid_cmd(uuid, "/get_class_name"), ['class_name'], []).class_name, class_name)
45 if path is not None:
46 self.assertEqual(cbox.GetThings(path + "/status", ['uuid'], []).uuid, uuid)
47 self.assertEqual(cbox.GetThings(Document.uuid_cmd(uuid, "/status"), ['uuid'], []).uuid, uuid)
49 def test_scene(self):
50 scene = Document.get_scene()
51 self.assertEqual(Document.get_engine().status().scenes[0], scene)
53 scene.clear()
54 scene.add_new_instrument_layer("test_instr", "sampler")
56 scene_status = scene.status()
57 layer = scene_status.layers[0]
58 self.verify_uuid(scene.uuid, "cbox_scene", "/scene")
59 self.verify_uuid(layer.uuid, "cbox_layer", "/scene/layer/1")
61 layers = scene.status().layers
62 self.assertEqual(len(layers), 1)
63 self.assertEqual(layers[0].uuid, layer.uuid)
64 layers[0].set_consume(0)
65 self.assertEqual(layers[0].status().consume, 0)
66 layers[0].set_consume(1)
67 self.assertEqual(layers[0].status().consume, 1)
68 layers[0].set_enable(0)
69 self.assertEqual(layers[0].status().enable, 0)
70 layers[0].set_enable(1)
71 self.assertEqual(layers[0].status().enable, 1)
73 layer_status = layers[0].status()
74 instr_uuid = layer_status.instrument.uuid
75 iname = layer_status.instrument_name
76 self.assertEqual(iname, 'test_instr')
77 self.verify_uuid(instr_uuid, "cbox_instrument", "/scene/instr/%s" % iname)
79 aux = scene.load_aux("piano_reverb")
80 module = aux.slot.engine
81 self.verify_uuid(aux.uuid, "cbox_aux_bus", "/scene/aux/piano_reverb")
82 scene.delete_aux("piano_reverb")
84 def test_aux_scene(self):
85 engine = Document.new_engine(44100, 1024)
86 scene = engine.new_scene()
87 self.assertEqual(engine.status().scenes[0], scene)
88 scene.add_instrument_layer("vintage")
89 scene_status = scene.status()
90 layer = scene_status.layers[0]
91 self.verify_uuid(scene.uuid, "cbox_scene")
92 self.verify_uuid(layer.uuid, "cbox_layer", scene.make_path("/layer/1"))
94 layers = scene.status().layers
95 self.assertEqual(len(layers), 1)
96 self.assertEqual(layers[0].uuid, layer.uuid)
97 layers[0].set_consume(0)
98 self.assertEqual(layers[0].status().consume, 0)
99 layers[0].set_consume(1)
100 self.assertEqual(layers[0].status().consume, 1)
101 layers[0].set_enable(0)
102 self.assertEqual(layers[0].status().enable, 0)
103 layers[0].set_enable(1)
104 self.assertEqual(layers[0].status().enable, 1)
106 layer_status = layers[0].status()
107 instr_uuid = layer_status.instrument.uuid
108 iname = layer_status.instrument_name
109 self.verify_uuid(instr_uuid, "cbox_instrument", scene.make_path("/instr/%s" % iname))
111 aux = scene.load_aux("piano_reverb")
112 module = aux.slot.engine
113 self.verify_uuid(aux.uuid, "cbox_aux_bus", scene.make_path("/aux/piano_reverb"))
114 scene.delete_aux("piano_reverb")
115 scene2 = engine.new_scene()
116 with self.assertRaises(Exception) as context:
117 layer_status.instrument.move_to(scene2, 1)
118 self.assertEqual(str(context.exception), "Invalid position 2 (valid are 1..1 or 0 for append)")
119 layer_status.instrument.move_to(scene2, 0)
121 layers = scene.status().layers
122 self.assertEqual(len(layers), 0)
123 layers = scene2.status().layers
124 self.assertEqual(len(layers), 1)
125 scene.add_instrument_layer("vintage")
126 with self.assertRaises(Exception) as context:
127 layer_status.instrument.move_to(scene, 0)
128 self.assertEqual(str(context.exception), "Instrument 'vintage' already exists in target scene")
130 def test_sampler_api(self):
131 engine = Document.new_engine(44100, 1024)
132 scene = engine.new_scene()
133 scene.add_new_instrument_layer("temporary", "sampler")
134 scene_status = scene.status()
135 layer = scene_status.layers[0]
136 self.verify_uuid(scene.uuid, "cbox_scene")
137 self.verify_uuid(layer.uuid, "cbox_layer", scene.make_path("/layer/1"))
138 instrument = layer.get_instrument()
139 self.assertEqual(instrument.status().engine, "sampler")
141 program0 = instrument.engine.load_patch_from_file(0, 'synthbass.sfz', 'test_sampler_sfz_loader')
142 self.assertNotEqual(program0, None)
143 self.assertEqual(program0.status().in_use, 16)
144 program1 = instrument.engine.load_patch_from_string(0, '.', '<group> resonance=3 <region> unknown=123 key=36 sample=impulse.wav cutoff=1000 <region> key=37 cutoff=2000 sample=impulse.wav ', 'test_sfz_parser_trailing_spaces')
145 self.assertNotEqual(program1, None)
146 self.assertEqual(program1.status().in_use, 16)
147 self.assertEqual(program1.status().name, 'test_sfz_parser_trailing_spaces')
148 self.assertRegex(program1.get_regions()[0].as_string(), 'sample=.*impulse\.wav')
149 program2 = instrument.engine.load_patch_from_string(0, '.', '<group> resonance=3 <region> unknown=123 key=36 sample=impulse.wav cutoff=1000.5 <region> key=37 sample=impulse.wav cutoff=2000', 'test_sampler_api')
150 self.assertNotEqual(program2, None)
151 self.assertEqual(program2.status().in_use, 16)
152 try:
153 program1.status()
154 self.assertTrue(False)
155 except Exception as e:
156 self.assertTrue('UUID not found' in str(e))
157 patches = instrument.engine.get_patches()
158 patches_dict = {}
159 self.assertEqual(len(patches), 1)
160 for (patchid, patchdata) in patches.items():
161 patchname, program, patchchannelcount = patchdata
162 self.verify_uuid(program.uuid, 'sampler_program')
163 self.assertEqual(program.status().program_no, patchid)
164 self.assertEqual(program.status().name, 'test_sampler_api')
165 self.assertEqual(program.status().sample_dir, '.')
166 self.assertEqual(program.status().program_no, 0)
167 self.assertEqual(program.status().in_use, 16)
168 instrument.engine.set_patch(1, 0)
169 self.assertEqual(program.status().in_use, 16)
170 instrument.engine.set_patch(2, 0)
171 self.assertEqual(program.status().in_use, 16)
172 regions = program.get_regions()
173 patches_dict[patchid] = (patchname, len(regions))
174 for region in regions:
175 region_str = Document.map_uuid(region.uuid).as_string()
176 print (patchname, region.uuid, region_str)
177 if patchname == 'test_sampler_api':
178 self.assertTrue('impulse.wav' in region_str)
179 self.assertTrue('key=c' in region_str)
180 if 'key=c2' in region_str:
181 self.assertTrue('unknown=123' in region_str)
182 self.assertTrue('cutoff=1000.5' in region_str)
183 else:
184 self.assertFalse('unknown=123' in region_str)
185 self.assertTrue('cutoff=2000' in region_str)
186 program.add_control_init(11, 64)
187 self.assertTrue((11,64) in program.get_control_inits())
188 program.delete_control_init(11, 0)
189 program.add_control_init(11, 0)
190 program.add_control_init(11, 64)
191 self.assertTrue((11,0) in program.get_control_inits())
192 self.assertTrue((11,64) in program.get_control_inits())
193 program.delete_control_init(11, 0)
194 self.assertTrue((11,0) not in program.get_control_inits())
195 self.assertTrue((11,64) in program.get_control_inits())
196 program.delete_control_init(11, 0)
197 self.assertTrue((11,0) not in program.get_control_inits())
198 self.assertTrue((11,64) not in program.get_control_inits())
199 program.add_control_init(11, 0)
200 program.add_control_init(11, 64)
201 program.delete_control_init(11, -1)
202 self.assertTrue((11,0) not in program.get_control_inits())
203 self.assertTrue((11,64) not in program.get_control_inits())
204 self.assertEqual(patches_dict, {0 : ('test_sampler_api', 2)})
205 group = region.status().parent_group
206 self.assertTrue("resonance=3" in group.as_string())
207 region.set_param("cutoff", 9000)
208 self.assertTrue('cutoff=9000' in region.as_string())
209 region.set_param("sample", 'test.wav')
210 self.assertTrue('test.wav' in region.as_string())
211 region.set_param("key", '12')
212 self.assertTrue('key=c0' in region.as_string())
213 print (region.status())
214 print (group.as_string())
215 print (region.as_string())
216 print ("Engine:", instrument.engine)
217 print ("Patches:", instrument.engine.get_patches())
218 program3 = program2.clone_to(instrument, 1)
219 print ("Program 1")
220 print (program2.status(), program2)
221 print (program2.get_groups())
222 print ("Program 2")
223 print (program3.status(), program3)
224 print (program3.get_groups())
225 print (instrument.engine.get_patches())
226 program3.delete()
228 def test_rt(self):
229 rt = Document.get_rt()
230 self.assertEqual(cbox.GetThings(Document.uuid_cmd(rt.uuid, "/status"), ['uuid'], []).uuid, rt.uuid)
232 def test_recorder_api(self):
233 engine = Document.new_engine(44100, 512)
234 scene = engine.new_scene()
235 scene.add_new_instrument_layer("temporary", "sampler")
236 layer = scene.status().layers[0]
237 instr = layer.status().instrument
238 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
240 meter_uuid = cbox.GetThings("/new_meter", ['uuid'], []).uuid
241 instr.cmd('/output/1/rec_dry/attach', None, meter_uuid)
242 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [meter_uuid])
243 instr.cmd('/output/1/rec_dry/detach', None, meter_uuid)
244 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
245 if os.path.exists("test.wav"):
246 os.unlink('test.wav')
248 rec = engine.new_recorder('test.wav')
249 self.assertEqual(rec.status().filename, 'test.wav')
250 rec_uuid = rec.uuid
251 instr.cmd('/output/1/rec_dry/attach', None, rec_uuid)
252 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [rec_uuid])
253 instr.cmd('/output/1/rec_dry/detach', None, rec_uuid)
254 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
255 self.assertTrue(os.path.exists('test.wav'))
256 self.assertTrue(os.path.getsize('test.wav') < 512)
257 os.unlink('test.wav')
259 rec = engine.new_recorder('test.wav')
260 self.assertEqual(rec.status().filename, 'test.wav')
261 rec_uuid = rec.uuid
262 instr.cmd('/output/1/rec_dry/attach', None, rec_uuid)
263 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [rec_uuid])
264 data = struct.unpack_from("512f", engine.render_stereo(512))
265 instr.cmd('/output/1/rec_dry/detach', None, rec_uuid)
266 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
267 rec.delete()
268 self.assertTrue(os.path.exists('test.wav'))
269 self.assertTrue(os.path.getsize('test.wav') > 512 * 4 * 2)
271 def test_song(self):
272 song = Document.get_song()
273 song.clear()
274 tp = song.status()
275 self.assertEqual(tp.tracks, [])
276 self.assertEqual(tp.patterns, [])
277 self.assertEqual(tp.mtis, [])
279 track = song.add_track()
280 pattern = song.load_drum_pattern('pat1')
281 track.add_clip(0, 0, 192, pattern)
283 song = Document.get_song()
284 tp = song.status()
285 self.assertEqual(tp.tracks[0].name, 'Unnamed')
286 self.assertEqual(tp.patterns[0].name, 'pat1')
287 track = tp.tracks[0].track
288 pattern = tp.patterns[0].pattern
290 track.set_name("Now named")
291 self.assertEqual(track.status().name, 'Now named')
292 pattern.set_name("pat1alt")
293 self.assertEqual(pattern.status().name, 'pat1alt')
295 tp = song.status()
296 self.assertEqual(tp.tracks[0].name, 'Now named')
297 self.assertEqual(tp.patterns[0].name, 'pat1alt')
299 clips = track.status().clips
300 self.assertEqual(clips[0].pos, 0)
301 self.assertEqual(clips[0].offset, 0)
302 self.assertEqual(clips[0].length, 192)
303 self.assertEqual(clips[0].pattern, pattern)
304 clip1 = clips[0].clip
306 clip2 = track.add_clip(192, 96, 48, pattern)
308 clip2_data = clip2.status()
309 self.assertEqual(clip2_data.pos, 192)
310 self.assertEqual(clip2_data.offset, 96)
311 self.assertEqual(clip2_data.length, 48)
312 self.assertEqual(clip2_data.pattern, pattern)
314 clips = track.status().clips
315 self.assertEqual(clips, [cbox.ClipItem(0, 0, 192, pattern.uuid, clip1.uuid), cbox.ClipItem(192, 96, 48, pattern.uuid, clip2.uuid)])
317 clip1.delete()
319 clips = track.status().clips
320 self.assertEqual(clips, [cbox.ClipItem(192, 96, 48, pattern.uuid, clip2.uuid)])
322 def test_mti(self):
323 MtiItem = cbox.MtiItem
324 song = Document.get_song()
325 song.clear()
326 tp = song.status()
327 self.assertEqual(tp.tracks, [])
328 self.assertEqual(tp.patterns, [])
329 self.assertEqual(tp.mtis, [])
330 song.set_mti(0, 120.0)
331 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0)])
332 song.set_mti(60, 150.0)
333 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(60, 150.0, 0, 0)])
334 song.set_mti(90, 180.0)
335 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(60, 150.0, 0, 0), MtiItem(90, 180.0, 0, 0)])
336 song.set_mti(60, 180.0)
337 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(60, 180.0, 0, 0), MtiItem(90, 180.0, 0, 0)])
338 song.set_mti(65, 210.0)
339 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(60, 180.0, 0, 0), MtiItem(65, 210.0, 0, 0), MtiItem(90, 180.0, 0, 0)])
341 song.set_mti(60, 0.0, 0, 0)
342 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(65, 210.0, 0, 0), MtiItem(90, 180.0, 0, 0)])
343 song.set_mti(65, 0.0, 0, 0)
344 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(90, 180.0, 0, 0)])
345 song.set_mti(68, 0.0, 0, 0)
346 self.assertEqual(song.status().mtis, [MtiItem(0, 120.0, 0, 0), MtiItem(90, 180.0, 0, 0)])
347 song.set_mti(0, 0.0, 0, 0)
348 self.assertEqual(song.status().mtis, [MtiItem(0, 0, 0, 0), MtiItem(90, 180.0, 0, 0)])
349 song.set_mti(90, 0.0, 0, 0)
350 self.assertEqual(song.status().mtis, [MtiItem(0, 0, 0, 0)])
352 def test_error(self):
353 thrown = False
354 try:
355 Document.get_scene().cmd('transpose', None, cbox)
356 except ValueError as ve:
357 self.assertTrue("class 'module'" in str(ve))
358 thrown = True
359 self.assertTrue(thrown)
361 unittest.main()
363 cbox.stop_audio()
364 cbox.shutdown_engine()