Make sure program changes end up before notes on the same tick.
[calfbox.git] / test.py
blobc59aac33afeaab531e0d401b3b08d2f0ae021732
1 import os
2 import sys
3 import struct
4 import time
5 import unittest
7 from calfbox import cbox
8 cbox.init_engine("")
9 cbox.start_noaudio(44100)
11 cbox.Config.add_section("drumpattern:pat1", """
12 title=Straight - Verse
13 beats=4
14 track1=bd
15 track2=sd
16 track3=hh
17 track4=ho
18 bd_note=c1
19 sd_note=d1
20 hh_note=f#1
21 ho_note=a#1
22 bd_trigger=9... .... 9.6. ....
23 sd_trigger=.... 9..5 .2.. 9...
24 hh_trigger=9353 7353 7353 73.3
25 ho_trigger=.... .... .... ..3.
26 """)
27 cbox.Config.add_section("fxpreset:piano_reverb", """
28 engine=reverb
29 """)
30 cbox.Config.add_section("instrument:vintage", """
31 engine=sampler
32 """)
34 global Document
35 Document = cbox.Document
37 Document.dump()
39 class TestCbox(unittest.TestCase):
40 def verify_uuid(self, uuid, class_name, path = None):
41 self.assertEqual(cbox.GetThings(Document.uuid_cmd(uuid, "/get_class_name"), ['class_name'], []).class_name, class_name)
42 if path is not None:
43 self.assertEqual(cbox.GetThings(path + "/status", ['uuid'], []).uuid, uuid)
44 self.assertEqual(cbox.GetThings(Document.uuid_cmd(uuid, "/status"), ['uuid'], []).uuid, uuid)
46 def test_scene(self):
47 scene = Document.get_scene()
49 scene.clear()
50 scene.add_new_instrument_layer("test_instr", "sampler")
52 scene_status = scene.status()
53 layer = scene_status.layers[0]
54 self.verify_uuid(scene.uuid, "cbox_scene", "/scene")
55 self.verify_uuid(layer.uuid, "cbox_layer", "/scene/layer/1")
57 layers = scene.status().layers
58 self.assertEqual(len(layers), 1)
59 self.assertEqual(layers[0].uuid, layer.uuid)
60 layers[0].set_consume(0)
61 self.assertEqual(layers[0].status().consume, 0)
62 layers[0].set_consume(1)
63 self.assertEqual(layers[0].status().consume, 1)
64 layers[0].set_enable(0)
65 self.assertEqual(layers[0].status().enable, 0)
66 layers[0].set_enable(1)
67 self.assertEqual(layers[0].status().enable, 1)
69 layer_status = layers[0].status()
70 instr_uuid = layer_status.instrument_uuid
71 iname = layer_status.instrument_name
72 self.assertEqual(iname, 'test_instr')
73 self.verify_uuid(instr_uuid, "cbox_instrument", "/scene/instr/%s" % iname)
75 aux = scene.load_aux("piano_reverb")
76 module = aux.get_slot_engine()
77 self.verify_uuid(aux.uuid, "cbox_aux_bus", "/scene/aux/piano_reverb")
78 scene.delete_aux("piano_reverb")
80 def test_aux_scene(self):
81 scene = Document.new_scene(44100, 1024)
82 scene.add_instrument_layer("vintage")
83 scene_status = scene.status()
84 layer = scene_status.layers[0]
85 self.verify_uuid(scene.uuid, "cbox_scene")
86 self.verify_uuid(layer.uuid, "cbox_layer", scene.make_path("/layer/1"))
88 layers = scene.status().layers
89 self.assertEqual(len(layers), 1)
90 self.assertEqual(layers[0].uuid, layer.uuid)
91 layers[0].set_consume(0)
92 self.assertEqual(layers[0].status().consume, 0)
93 layers[0].set_consume(1)
94 self.assertEqual(layers[0].status().consume, 1)
95 layers[0].set_enable(0)
96 self.assertEqual(layers[0].status().enable, 0)
97 layers[0].set_enable(1)
98 self.assertEqual(layers[0].status().enable, 1)
100 layer_status = layers[0].status()
101 instr_uuid = layer_status.instrument_uuid
102 iname = layer_status.instrument_name
103 self.verify_uuid(instr_uuid, "cbox_instrument", scene.make_path("/instr/%s" % iname))
105 aux = scene.load_aux("piano_reverb")
106 module = aux.get_slot_engine()
107 self.verify_uuid(aux.uuid, "cbox_aux_bus", scene.make_path("/aux/piano_reverb"))
108 scene.delete_aux("piano_reverb")
110 def test_sampler_api(self):
111 scene = Document.new_scene(44100, 1024)
112 scene.add_new_instrument_layer("temporary", "sampler")
113 scene_status = scene.status()
114 layer = scene_status.layers[0]
115 self.verify_uuid(scene.uuid, "cbox_scene")
116 self.verify_uuid(layer.uuid, "cbox_layer", scene.make_path("/layer/1"))
117 instrument = layer.get_instrument()
118 self.assertEqual(instrument.status().engine, "sampler")
120 # XXXKF: this lack of stable Python representation of engines is annoying
121 #instrument.cmd('/engine/load_patch_from_string', None, 1, '.', '<region> key=36 sample=impulse.wav cutoff=1000 <region> key=37 sample=impulse.wav cutoff=2000', 'test_sampler_api')
122 instrument.cmd('/engine/load_patch_from_string', None, 0, '.', '<group> resonance=3 <region> unknown=123 key=36 sample=impulse.wav cutoff=1000 <region> key=37 sample=impulse.wav cutoff=2000', 'test_sampler_api')
123 patches = instrument.get_things("/engine/patches", ["*patch"]).patch
124 patches_dict = {}
125 for patch in patches:
126 patchid, patchname, patchuuid, patchchannelcount = patch
127 self.verify_uuid(patchuuid, 'sampler_program')
128 program = Document.map_uuid(patchuuid)
129 self.verify_uuid(program.uuid, 'sampler_program')
130 self.assertEqual(program.uuid, patchuuid)
131 regions = program.get_things("/regions", ["*region"]).region
132 patches_dict[patchid] = (patchname, len(regions))
133 for region_uuid in regions:
134 region_str = Document.map_uuid(region_uuid).as_string()
135 print (patchname, region_uuid, region_str)
136 if patchname == 'test_sampler_api':
137 self.assertTrue('impulse.wav' in region_str)
138 self.assertTrue('key=c' in region_str)
139 if 'key=c2' in region_str:
140 self.assertTrue('unknown=123' in region_str)
141 self.assertTrue('cutoff=1000' in region_str)
142 else:
143 self.assertFalse('unknown=123' in region_str)
144 self.assertTrue('cutoff=2000' in region_str)
145 self.assertEqual(patches_dict, {0 : ('test_sampler_api', 2)})
146 region = Document.map_uuid(region_uuid)
147 group = Document.map_uuid(region.status().parent_group)
148 self.assertTrue("resonance=3" in group.as_string())
149 region.set_param("cutoff", 9000)
150 self.assertTrue('cutoff=9000' in region.as_string())
151 region.set_param("sample", 'test.wav')
152 self.assertTrue('test.wav' in region.as_string())
153 region.set_param("key", '12')
154 self.assertTrue('key=c0' in region.as_string())
155 print (region.status())
156 print (group.as_string())
157 print (region.as_string())
159 def test_rt(self):
160 rt = Document.get_rt()
161 self.assertEqual(cbox.GetThings(Document.uuid_cmd(rt.uuid, "/status"), ['uuid'], []).uuid, rt.uuid)
163 def test_recorder_api(self):
164 scene = Document.get_scene()
165 scene.clear()
166 scene.add_new_instrument_layer("temporary", "sampler")
167 layer = scene.status().layers[0]
168 instr = Document.map_uuid(layer.status().instrument_uuid)
169 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
171 meter_uuid = cbox.GetThings("/new_meter", ['uuid'], []).uuid
172 instr.cmd('/output/1/rec_dry/attach', None, meter_uuid)
173 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [meter_uuid])
174 instr.cmd('/output/1/rec_dry/detach', None, meter_uuid)
175 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
177 rec_uuid = cbox.GetThings("/new_recorder", ['uuid'], ['test.wav']).uuid
178 instr.cmd('/output/1/rec_dry/attach', None, rec_uuid)
179 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [rec_uuid])
180 instr.cmd('/output/1/rec_dry/detach', None, rec_uuid)
181 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
182 self.assertTrue(os.path.exists('test.wav'))
183 self.assertTrue(os.path.getsize('test.wav') < 512)
185 rec_uuid = cbox.GetThings("/new_recorder", ['uuid'], ['test.wav']).uuid
186 instr.cmd('/output/1/rec_dry/attach', None, rec_uuid)
187 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [rec_uuid])
188 data = struct.unpack_from("512f", cbox.GetThings("/scene/render_stereo", ['data'], [512]).data)
189 instr.cmd('/output/1/rec_dry/detach', None, rec_uuid)
190 self.assertEqual(instr.get_things("/output/1/rec_dry/status", ['*handler']).handler, [])
191 self.assertTrue(os.path.exists('test.wav'))
192 self.assertTrue(os.path.getsize('test.wav') > 512 * 4 * 2)
194 def test_song(self):
195 song = Document.get_song()
196 song.clear()
197 tp = song.status()
198 self.assertEqual(tp.tracks, [])
199 self.assertEqual(tp.patterns, [])
200 self.assertEqual(tp.mtis, [])
202 track = song.add_track()
203 pattern = song.load_drum_pattern('pat1')
204 track.add_clip(0, 0, 192, pattern)
206 song = Document.get_song()
207 tp = song.status()
208 self.assertEqual(tp.tracks[0].name, 'Unnamed')
209 self.assertEqual(tp.patterns[0].name, 'pat1')
210 track = tp.tracks[0].track
211 pattern = tp.patterns[0].pattern
213 track.set_name("Now named")
214 self.assertEqual(track.status().name, 'Now named')
215 pattern.set_name("pat1alt")
216 self.assertEqual(pattern.status().name, 'pat1alt')
218 tp = song.status()
219 self.assertEqual(tp.tracks[0].name, 'Now named')
220 self.assertEqual(tp.patterns[0].name, 'pat1alt')
222 clips = track.status().clips
223 self.assertEqual(clips[0].pos, 0)
224 self.assertEqual(clips[0].offset, 0)
225 self.assertEqual(clips[0].length, 192)
226 self.assertEqual(clips[0].pattern, pattern)
227 clip1 = clips[0].clip
229 clip2 = track.add_clip(192, 96, 48, pattern)
231 clip2_data = clip2.status()
232 self.assertEqual(clip2_data.pos, 192)
233 self.assertEqual(clip2_data.offset, 96)
234 self.assertEqual(clip2_data.length, 48)
235 self.assertEqual(clip2_data.pattern, pattern)
237 clips = track.status().clips
238 self.assertEqual(clips, [cbox.ClipItem(0, 0, 192, pattern.uuid, clip1.uuid), cbox.ClipItem(192, 96, 48, pattern.uuid, clip2.uuid)])
240 clip1.delete()
242 clips = track.status().clips
243 self.assertEqual(clips, [cbox.ClipItem(192, 96, 48, pattern.uuid, clip2.uuid)])
245 def test_mti(self):
246 song = Document.get_song()
247 song.clear()
248 tp = song.status()
249 self.assertEqual(tp.tracks, [])
250 self.assertEqual(tp.patterns, [])
251 self.assertEqual(tp.mtis, [])
252 song.set_mti(0, 120.0)
253 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0)])
254 song.set_mti(60, 150.0)
255 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (60, 150.0, 0, 0)])
256 song.set_mti(90, 180.0)
257 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (60, 150.0, 0, 0), (90, 180.0, 0, 0)])
258 song.set_mti(60, 180.0)
259 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (60, 180.0, 0, 0), (90, 180.0, 0, 0)])
260 song.set_mti(65, 210.0)
261 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (60, 180.0, 0, 0), (65, 210.0, 0, 0), (90, 180.0, 0, 0)])
263 song.set_mti(60, 0.0, 0, 0)
264 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (65, 210.0, 0, 0), (90, 180.0, 0, 0)])
265 song.set_mti(65, 0.0, 0, 0)
266 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (90, 180.0, 0, 0)])
267 song.set_mti(68, 0.0, 0, 0)
268 self.assertEqual(song.status().mtis, [(0, 120.0, 0, 0), (90, 180.0, 0, 0)])
269 song.set_mti(0, 0.0, 0, 0)
270 self.assertEqual(song.status().mtis, [(0, 0, 0, 0), (90, 180.0, 0, 0)])
271 song.set_mti(90, 0.0, 0, 0)
272 self.assertEqual(song.status().mtis, [(0, 0, 0, 0)])
274 def test_error(self):
275 thrown = False
276 try:
277 Document.get_scene().cmd('transpose', None, cbox)
278 except ValueError as ve:
279 self.assertTrue("class 'module'" in str(ve))
280 thrown = True
281 self.assertTrue(thrown)
283 unittest.main()
285 cbox.stop_audio()
286 cbox.shutdown_engine()