| Trees | Indices | Help |
|
|---|
|
|
1 from __future__ import absolute_import 2 """Harmonical input file processing. 3 4 Simple input reading to give tone specifications directly to the 5 harmonical. 6 7 """ 8 """ 9 ============================== License ======================================== 10 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 11 12 This file is part of The Jazz Parser. 13 14 The Jazz Parser is free software: you can redistribute it and/or modify 15 it under the terms of the GNU General Public License as published by 16 the Free Software Foundation, either version 3 of the License, or 17 (at your option) any later version. 18 19 The Jazz Parser is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 GNU General Public License for more details. 23 24 You should have received a copy of the GNU General Public License 25 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>. 26 27 ============================ End license ====================================== 28 29 """ 30 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>" 31 32 from jazzparser.utils.base import abstractmethod 33 from .tones import ToneMatrix, SineChordEvent, SineClusterEvent, ENVELOPES 34 from .midi import tonal_space_note_events 35 from .midi.chords import events_for_chord 36 from jazzparser.utils.tonalspace import tonal_space_et_pitch, tonal_space_pitch 37 from jazzparser.data import Fraction 38 from midi import write_midifile, EventStream, SetTempoEvent, \ 39 ProgramChangeEvent, NoteOnEvent, NoteOffEvent42 """ 43 Base class for input files to the harmonical. 44 45 """ 46 @staticmethod14048 """ 49 Read in an input file. This method detects the type of input 50 and returns an instance of the appropriate subclass. 51 52 @type formats: list of strings 53 @param formats: list of formats to allow. By default, accepts any 54 format. If a list is given and the file's format isn't in it, 55 raises an error. 56 57 """ 58 infile = open(filename, 'r') 59 lines = infile.readlines() 60 # Remove all comments from the lines 61 lines = [line.partition("#")[0].strip() for line in lines] 62 # Remove empty lines 63 lines = [line for line in lines if len(line) > 0] 64 # Make sure there's some content - a type line and at least one more 65 if len(lines) < 2: 66 raise HarmonicalInputFileReadError, "empty file" 67 # Read any global directives and don't pass them through to the subclass 68 dirlines = [line.lstrip("@") for line in lines if line.startswith("@")] 69 directives = {} 70 for dirline in dirlines: 71 name,__,value = dirline.partition(":") 72 value = value.strip() 73 directives[name] = value 74 75 # Check the @format directive is given 76 if 'format' not in directives: 77 raise HarmonicalInputFileReadError, "no format descriptor "\ 78 "found in the input file." 79 format = directives.pop('format') 80 # Make sure this is a format we know about 81 if format not in INPUT_TYPES: 82 raise HarmonicalInputFileReadError, "invalid harmonic "\ 83 "input format: '%s'. Should be one of: %s" % \ 84 (format, ", ".join(INPUT_TYPES.keys())) 85 if formats is not None and format not in formats: 86 raise HarmonicalInputFileReadError, "harmonical input format "\ 87 "%s not allowed here. You can only use %s" % (format, 88 ", ".join(formats)) 89 90 lines = [line for line in lines if not line.startswith("@")] 91 # First process the directives 92 directives = INPUT_TYPES[format].process_global_directives(directives) 93 # Use the subclass' loader to get an appropriate instance 94 inst = INPUT_TYPES[format].from_data(lines, directives) 95 return inst96 97 @staticmethod99 output = {} 100 unused = data.keys() 101 if "tempo" in data: 102 output["tempo"] = int(data["tempo"]) 103 unused.remove("tempo") 104 if len(unused) != 0: 105 raise HarmonicalInputFileReadError, "unknown global " \ 106 "directive '%s'" % unused[0] 107 return output108 109 @staticmethod 110 @abstractmethod112 """ 113 Subclasses must provide this method to create an instance from 114 the lines of data read in from a file. 115 Should take a second argument which will be a dictionary of 116 the global directive values. 117 118 """ 119 pass120 121 @abstractmethod 128 129 @abstractmethod144 """Get a 'for' duration specification out of a line's tokens. 145 Returns duration in quarter notes.""" 146 duration = 1 147 if "for" in tokens: 148 forpos = tokens.index("for") 149 try: 150 duration = float(Fraction(tokens.pop(forpos+1))) 151 except: 152 raise HarmonicalInputFileReadError, "'for' "\ 153 "must be followed by an integer number "\ 154 "of beats." 155 tokens.pop(forpos) 156 return duration158 """ 159 Converts from quarter note duration to number of seconds. 160 """ 161 return float(duration) * 60 / directives['tempo']162164 """Get a 'at' root specification out of a line's tokens.""" 165 if "at" in tokens: 166 atpos = tokens.index("at") 167 try: 168 root = _read_coord(tokens.pop(atpos+1)) 169 except: 170 raise HarmonicalInputFileReadError, "'at' "\ 171 "must be followed by an integer number "\ 172 "of beats." 173 tokens.pop(atpos) 174 else: 175 root = (0,0,0) 176 return root177179 """Parse a 3D coordinate string.""" 180 if not token.startswith("(") or not token.endswith(")"): 181 raise HarmonicalInputFileReadError, \ 182 "invalid coordinate: %s" % token 183 parts = token[1:-1].split(",") 184 if len(parts) != 3: 185 raise HarmonicalInputFileReadError, \ 186 "coordinate must be 3D: %s" % token 187 return int(parts[0]), int(parts[1]), int(parts[2])188190 """Get the pitch of a point in the space.""" 191 if state['equal_temperament']: 192 return state['origin'] * tonal_space_et_pitch(point) 193 else: 194 return state['origin'] * tonal_space_pitch(point)195197 """Reads a volume specifier.""" 198 if "vol" in tokens: 199 atpos = tokens.index("vol") 200 try: 201 volume = int(tokens.pop(atpos+1)) 202 except ValueError: 203 raise HarmonicalInputFileReadError, "'vol' "\ 204 "must be followed by an integer volume "\ 205 "between 0 and 100." 206 if volume < 0 or volume > 100: 207 raise HarmonicalInputFileReadError, "volume "\ 208 "value must be between 0 and 100, not %s" % volume 209 tokens.pop(atpos) 210 volume = float(volume)/100 211 else: 212 volume = 0.8 213 return volume214215 ############################################### 216 217 218 -class ChordInputFile(HarmonicalInputFile):219 """ 220 Type of harmonical input file that simply specifies a list of 221 chords, with durations, defined by a list of tonal space points. 222 223 For documentation of the file syntax, see 224 U{http://jazzparser.granroth-wilding.co.uk/HarmonicalChordInput}. 225 226 """ 230419232 # This is built when the file's read, so just return it now 233 matrix = self.__tone_matrix 234 # Now render the matrix to audio 235 return matrix.render()236238 # Midi file is actually generated during parsing, so we just return it 239 return self.__midi_file240 241 @staticmethod243 # Build the tone matrix straight up 244 state = { 245 'equal_temperament' : False, 246 'double_root' : False, 247 'envelope' : None, 248 'origin' : 440, 249 'time' : 0.0, 250 } 251 tone_matrix = ToneMatrix() 252 253 #### Prepare a midi stream 254 mid = EventStream() 255 mid.add_track() 256 # Add a tempo event 257 tempo = SetTempoEvent() 258 tempo.tempo = directives['tempo'] 259 tempo.tick = 0 260 mid.add_event(tempo) 261 262 # Each line represents a single chord or some kind of directive 263 for line in data: 264 first_word = line.split()[0].lower() 265 if "=" in line: 266 # This is an assignment 267 key, __, value = line.partition("=") 268 key = key.strip() 269 value = value.strip() 270 # Check it's valid 271 if key == "equal_temperament": 272 if value not in ['off','on']: 273 raise HarmonicalInputFileReadError, \ 274 "equal_temperament must be 'off' or 'on', "\ 275 "not '%s'" % value 276 value = (value == 'on') 277 elif key == "origin": 278 try: 279 value = int(value) 280 except ValueError: 281 # Try interpreting as a coordinate 282 try: 283 coord = _read_coord(value) 284 except HarmonicalInputFileReadError: 285 raise HarmonicalInputFileReadError, "origin "\ 286 "value must be an integer or a coordinate." 287 value = _get_pitch(coord) 288 elif key == "double_root": 289 if value not in ['off','on']: 290 raise HarmonicalInputFileReadError, \ 291 "double_root must be 'off' or 'on', "\ 292 "not '%s'" % value 293 value = (value == 'on') 294 elif key == "envelope": 295 if value not in ENVELOPES: 296 raise HarmonicalInputFileReadError, "unknown "\ 297 "envelope '%s'. Must be one of: %s" % \ 298 (value, ", ".join(ENVELOPES.keys())) 299 value = ENVELOPES[value]() 300 elif key == "program": 301 # Midi program change 302 try: 303 value = int(value) 304 if value > 127 or value < 0: 305 raise ValueError 306 except ValueError: 307 raise HarmonicalInputFileReadError, "invalid program "\ 308 "change: %s. Should be an integer 0-127" % value 309 pchange = ProgramChangeEvent() 310 pchange.value = value 311 pchange.tick = int(state['time'] * mid.resolution) 312 mid.add_event(pchange) 313 else: 314 raise HarmonicalInputFileReadError, "invalid "\ 315 "assignment key: '%s'" % key 316 # Make this assignment when we get to it in the score 317 state[key] = value 318 elif first_word == "rest": 319 tokens = line.split() 320 duration = _get_duration(tokens) 321 322 # Just move the time counter on without doing anything 323 state['time'] += duration 324 elif first_word == "chord": 325 tokens = line.lstrip("chord").split() 326 duration = _get_duration(tokens) 327 root = _get_root(tokens) 328 volume = _get_volume(tokens) 329 sec_duration = _qn_to_seconds(duration) 330 331 # Must be just a chord type left 332 if len(tokens) > 1: 333 raise HarmonicalInputFileReadError, "chord must "\ 334 "include just a chord type" 335 if len(tokens) == 0: 336 ctype = '' 337 else: 338 ctype = tokens[0] 339 340 # Add the chord to the tone matrix 341 tone_matrix.add_tone( 342 _qn_to_seconds(state['time']), 343 SineChordEvent( 344 _get_pitch(root), 345 ctype, 346 duration=sec_duration, 347 amplitude=volume, 348 equal_temperament=state['equal_temperament'], 349 double_root=state['double_root'], 350 envelope=state['envelope'] 351 ) 352 ) 353 354 # Add the same chord to the midi file 355 tick_time = int(mid.resolution * state['time']) 356 tick_duration = int(duration * mid.resolution) 357 # TODO: currently this will always treat C as the origin 358 # even if you change it with directives 359 events = events_for_chord(root, 360 ctype, 361 tick_time, 362 tick_duration, 363 velocity = int(volume*127), 364 equal_temperament=state['equal_temperament'], 365 double_root=state['double_root']) 366 for ev in events: 367 mid.add_event(ev) 368 369 # Move the timer on ready for the next chord 370 state['time'] += duration 371 elif first_word in ["tones", "t"]: 372 # Must be a chord made up of coordinates 373 tokens = line.lstrip("tones").split() 374 duration = _get_duration(tokens) 375 root = _get_root(tokens) 376 volume = _get_volume(tokens) 377 sec_duration = _qn_to_seconds(duration) 378 379 # The rest should be the list of coordinates 380 coordinates = [_read_coord(token) for token in tokens] 381 382 # Add the chord to the tone matrix 383 tone_matrix.add_tone( 384 _qn_to_seconds(state['time']), 385 SineClusterEvent( 386 _get_pitch(root), 387 coordinates, 388 duration=sec_duration, 389 amplitude=volume, 390 equal_temperament=state['equal_temperament'], 391 double_root=state['double_root'], 392 envelope=state['envelope'] 393 ) 394 ) 395 396 397 # Add the same chord to the midi file 398 tick_time = int(mid.resolution * state['time']) 399 tick_duration = int(duration * mid.resolution) 400 for note in coordinates: 401 # TODO: currently this will always treat C as the origin 402 # even if you change it with directives 403 events = tonal_space_note_events(note, 404 tick_time, 405 tick_duration, 406 velocity = int(volume*127)) 407 if state['equal_temperament']: 408 # Omit tuning event (the first one) 409 events = events[1:] 410 for ev in events: 411 mid.add_event(ev) 412 413 # Move the timer on ready for the next chord 414 state['time'] += duration 415 else: 416 raise HarmonicalInputFileReadError, "could not make sense "\ 417 "of the line: %s" % line 418 return ChordInputFile(tone_matrix, midi_file=mid)422 """ 423 Rather like the ChordInputFile format, but specifies a list of chords 424 with names. This is used by the interactive tonal space to specify 425 chord clusters. 426 427 For documentation of the file syntax, see 428 U{http://jazzparser.granroth-wilding.co.uk/HarmonicalChordVocab}. 429 430 """458 459 INPUT_TYPES = { 460 'chord' : ChordInputFile, 461 'chord-vocab' : ChordDictionaryFile, 462 } 466432 self.chords = chord_dict 433 if names is None: 434 self.chord_names = chord_dict.keys() 435 else: 436 self.chord_names = names437 438 @staticmethod440 chords = {} 441 names = [] 442 443 # Each line represents a single chord type (TS cluster), or some 444 # kind of directive 445 for line in data: 446 if ":" in line: 447 # Definition of a chord type 448 name, __, value = line.partition(":") 449 name = name.strip() 450 tokens = value.strip().split() 451 notes = [_read_coord(tok) for tok in tokens] 452 chords[name] = notes 453 names.append(name) 454 else: 455 raise HarmonicalInputFileReadError, "could not make sense "\ 456 "of the line: %s" % line 457 return ChordDictionaryFile(chords, names)
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Mon Nov 26 16:05:02 2012 | http://epydoc.sourceforge.net |