Songwrite3-0.1/ 0000755 0023421 0023421 00000000000 13155312424 013250 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/player.py 0000664 0023421 0023421 00000007545 12731517241 015136 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2001-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, subprocess, os, fcntl
from io import BytesIO
import PyQt5.QtCore as qtcore
import editobj3
import songwrite3, songwrite3.globdef as globdef
_p = None
_midi = ""
_loop = 0
_tracker = None
_last_tracker = None
_output = b""
def is_playing(): return _p and (_p.poll() is None)
def stop():
global _p, _tracker
if not _p is None:
if _tracker:
_tracker(-1)
_tracker = None
try:
_p.terminate()
_p.kill()
except OSError:
sys.excepthook(*sys.exc_info())
_p = None
def play(midi, loop = 0, tracker = None):
global _p, _midi, _loop, _tracker, _last_tracker, _output
stop()
if not globdef.config.DISPLAY_PLAY_BAR: tracker = None
_midi = midi
_loop = loop
_tracker = _last_tracker = tracker
_output = b""
_play()
def _play():
global _p, _midi, _loop, _tracker
if globdef.config.MIDI_USE_TEMP_FILE:
import tempfile
file = tempfile.mktemp(".midi")
open(file, "w").write(_midi)
_p = p = subprocess.Popen(globdef.config.MIDI_COMMAND % file, shell = True, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
else:
_p = p = subprocess.Popen(globdef.config.MIDI_COMMAND, shell = True, stdin = subprocess.PIPE, stdout = subprocess.PIPE)
_p.stdin.write(_midi)
_p.stdin.close()
fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
try: _play.qt_timer.stop()
except: pass
_play.qt_timer = qtcore.QTimer()
_play.qt_timer.timeout.connect(on_timer)
_play.qt_timer.setSingleShot(False)
if _tracker: _play.qt_timer.setInterval( 50)
else: _play.qt_timer.setInterval(300)
_play.qt_timer.start()
def on_timer():
global _tracker, _output
try:
if _p is None:
_play.qt_timer.stop() #return 0 # Stop was called
return
r = _p.poll()
if r is None:
if _tracker:
output = _p.stdout.read()
if output:
lines = (_output + output).split(b"\n")
_output = lines[-1]
if len(lines) > 1:
line = lines[-2]
try:
if b":" in line: t = int(line[line.rfind(b":") + 3 :])
else: t = int(line)
except: return # 1
_tracker(t)
if t == -1:
_tracker = None # Tracking has reached its end !
stop()
if _loop:
_tracker = _last_tracker
_play()
else:
_play.qt_timer.stop()
if (r == 0) and _loop:
_tracker = _last_tracker
_play()
except: sys.excepthook(*sys.exc_info())
noteplayer = None
def play_note(instrument, value):
if not globdef.config.PLAY_AS_TYPING: return
stop()
global noteplayer
if not noteplayer:
noteplayer = songwrite3.model.Song()
noteplayer.partitions.append(songwrite3.model.Partition(noteplayer))
noteplayer.partitions[0].notes.append(songwrite3.model.Note(noteplayer.partitions[0], 0, 48, 0))
noteplayer.partitions[0].instrument = instrument
noteplayer.partitions[0].notes[0].value = value
play(songwrite3.midi.song_2_midi(noteplayer))
Songwrite3-0.1/plugins/ 0000755 0023421 0023421 00000000000 13155312423 014730 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/chord/ 0000755 0023421 0023421 00000000000 13155312423 016027 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/chord/drawer.py 0000664 0023421 0023421 00000041662 12716333213 017702 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2011 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
from editobj3.undoredo import *
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
import songwrite3.model as model
import songwrite3.globdef as globdef
import songwrite3.canvas as canvas
from songwrite3.plugins.chord.chord import ChordManager, TablatureChordManager, LyreChordManager, PianoChordManager, AccordionChordManager
def set_chord(canvas, partition, chord_manager, times, base, second = 0, third = 1, fourth = 0, fifth = 7, sixth = 0, seventh = 0):
if third == 1: third = chord_manager.choose_third(partition, base)
#if ((base + 4 - model.OFFSETS[partition.tonality]) % 12) in model.NOTES: third = 4
#else: third = 3
if not isinstance(times, dict): # times should be a dict mapping each time to the reference note for it
times2 = {}
for time in times:
notes = partition.notes_at(time)
times2[time] = notes[0]
times = times2
values, frets = chord_manager.create(base, second, third, fourth, fifth, sixth, seventh)
saved = {}
saved_duration = {}
removed = []
new_notes = []
def do_it(saved_duration = saved_duration):
for time, ref in times.items():
notes = partition.notes_at(time)
i = 0
chord_notes = []
for value, string_id in values:
if i < len(notes):
saved[notes[i]] = notes[i].value, getattr(notes[i], "string_id", 0)
notes[i].value = value
if string_id != -1:
notes[i].string_id = string_id
chord_notes.append(notes[i])
if canvas:
canvas.auto_update_duration(notes[i], saved_duration)
canvas.auto_update_duration(notes[i].partition.note_after(notes[i]), saved_duration)
i += 1
else:
new_note = model.Note(partition, time, ref.duration, value, ref.volume)
if string_id != -1:
new_note.string_id = string_id
new_note.fx = ref.fx
new_note.link_fx = ref.link_fx
new_note.duration_fx = ref.duration_fx
new_note.strum_dir_fx = ref.strum_dir_fx
new_notes .append(new_note)
chord_notes.append(new_note)
if len(values) < len(notes):
removed.extend(notes[len(values) - len(notes) :])
partition.remove(*removed)
partition.add_note(*new_notes)
if canvas:
for new_note in new_notes:
canvas.auto_update_duration(new_note, saved_duration)
canvas.auto_update_duration(new_note.partition.note_after(new_note), saved_duration)
canvas.deselect_all()
for note in chord_notes: canvas.partition_2_drawer[partition].select_note(note)
def undo_it(saved_duration = saved_duration):
for note, (value, string_id) in saved.items():
note.value = value
note.string_id = string_id
if removed : partition.add_note(*removed)
if new_notes: partition.remove (*new_notes)
if canvas: canvas.restore_saved_duration(saved_duration)
saved .clear()
saved_duration.clear()
removed .__imul__(0)
new_notes .__imul__(0)
return do_it, undo_it
class ChordDrawer(canvas.PartitionDrawer):
avoid_repeat = 1
always_draw_stems = 1
need_extra_space_for_strum_dir = 0
def __init__(self, canvas_, partition, compact = False):
canvas.PartitionDrawer.__init__(self, canvas_, partition, compact)
self.strings = [SingleString(self)]
self.string_height = self.canvas.default_line_height * 1.3
self.stem_extra_height = self.canvas.default_line_height
self.chord_manager = self.ChordManager(partition)
self.last_chord_name = None
self.last_chord_mesure = None
self.note_offset_x = 3.0 * self.scale
def config_listener(self, obj, type, new, old):
canvas.PartitionDrawer.config_listener(self, obj, type, new, old)
if type is object:
if (new.get("ENGLISH_CHORD_NAME") != old.get("ENGLISH_CHORD_NAME")):
self.chord_manager = self.ChordManager(self.partition)
self.canvas.render_all()
def partition_listener(self, partition, type, new, old):
canvas.PartitionDrawer.partition_listener(self, partition, type, new, old)
if type is object:
if (new.get("tonality") != old.get("tonality")):
self.chord_manager = self.ChordManager(self.partition)
self.canvas.render_all()
def on_touchscreen_new_note(self, event): return False
def note_width(self, note): return 9.0 * self.scale
def select_note(self, note):
notes = self.partition.notes_at(note)
if not note in notes:
self.canvas.add_selection(note, *self.note_dimensions(note))
for note in notes:
self.canvas.add_selection(note, *self.note_dimensions(note))
def on_key_press(self, event):
keyval = event.key()
if qtcore.Qt.Key_A <= keyval <= qtcore.Qt.Key_G: # a-g
value = model.NOTES[(keyval - qtcore.Qt.Key_A + 5) % 7]
if value in model.TONALITIES[self.partition.tonality]:
if model.TONALITIES[self.partition.tonality][0] == model.DIESES[0]:
value += 1
else:
value -= 1
times = {}
for note in self.canvas.selections: times[note.time] = note
do_it, undo_it = set_chord(self.canvas, self.partition, self.chord_manager, times, value)
UndoableOperation(do_it, undo_it, _("add chord"), self.canvas.main.undo_stack)
return 1
elif keyval == qtcore.Qt.Key_M: # m
self.toggle_major()
return 1
elif qtcore.Qt.Key_0 <= keyval <= qtcore.Qt.Key_9: # Numbers
nb = keyval - qtcore.Qt.Key_0
if nb == 5: self.toggle_5()
if nb == 7: self.toggle_7()
return 1
def toggle_major(self):
times = {}
for note in self.canvas.selections: times[note.time] = 1
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = self.chord_manager.identify(rel_values)
if chord:
third = chord.identified_notes[2]
if third == 3: third = 4; break
else: third = 3; break
else: third = 4
do_its = []
undo_its = []
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = self.chord_manager.identify(rel_values)
v = [value or 0 for value in chord.identified_notes]
#do_it1, undo_it1 = self.set_chord([time], chord.base, v[1], third, v[3], v[4], v[5], v[6], return_func = 1)
do_it1, undo_it1 = set_chord(self.canvas, self.partition, self.chord_manager, [time], chord.base, v[1], third, v[3], v[4], v[5], v[6])
do_its .append( do_it1)
undo_its.append(undo_it1)
def do_it():
for func in do_its: func()
def undo_it():
for func in undo_its: func()
UndoableOperation(do_it, undo_it, _("add chord"), self.canvas.main.undo_stack)
def toggle_5(self):
times = {}
for note in self.canvas.selections: times[note.time] = 1
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = self.chord_manager.identify(rel_values)
if chord:
third = chord.identified_notes[2]
if third: third = 0; break
else: third = 1
do_its = []
undo_its = []
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = self.chord_manager.identify(rel_values)
v = [value or 0 for value in chord.identified_notes]
do_it1, undo_it1 = set_chord(self.canvas, self.partition, self.chord_manager, [time], chord.base, v[1], third, v[3], v[4], v[5], v[6])
do_its .append( do_it1)
undo_its.append(undo_it1)
def do_it():
for func in do_its: func()
def undo_it():
for func in undo_its: func()
UndoableOperation(do_it, undo_it, _("add chord"), self.canvas.main.undo_stack)
def toggle_7(self):
times = {}
for note in self.canvas.selections: times[note.time] = 1
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = self.chord_manager.identify(rel_values)
if chord:
seventh = chord.identified_notes[6]
if seventh: seventh = 0; break
else: seventh = 10
do_its = []
undo_its = []
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = self.chord_manager.identify(rel_values)
v = [value or 0 for value in chord.identified_notes]
#do_it1, undo_it1 = self.set_chord([time], chord.base, v[1], v[2], v[3], v[4], v[5], seventh, return_func = 1)
do_it1, undo_it1 = set_chord(self.canvas, self.partition, self.chord_manager, [time], chord.base, v[1], v[2], v[3], v[4], v[5], seventh)
do_its .append( do_it1)
undo_its.append(undo_it1)
def do_it():
for func in do_its: func()
def undo_it():
for func in undo_its: func()
UndoableOperation(do_it, undo_it, _("add chord"), self.canvas.main.undo_stack)
# No "string" notion in chord notation!
def note_string_id (self, note ): return 0
def default_string_id(self, value): return 0
def y_2_string_id(self, y, force = 0):
if force:
if self.y + self.start_y > y: return -1
elif y > self.y + self.height: return 1
return 0
else:
if self.y + self.start_y <= y <= self.y + self.height: return 0
return None
def string_id_2_y(self, string_id = 0):
return self.y + self.start_y + self.stem_extra_height + self.string_height // 2
def draw(self, ctx, x, y, width, height, drag = 0, draw_icon = 1):
if self.compact:
canvas.PartitionDrawer.draw(self, ctx, x, y, width, height, drag, 0, -11.0)
else:
canvas.PartitionDrawer.draw(self, ctx, x, y, width, height, drag, 0, -11.0)
def draw_mesure(self, ctx, time, mesure, y, height):
canvas.PartitionDrawer.draw_mesure(self, ctx, time, mesure, y, height, False)
#pass
def draw_strings(self, ctx, x, y, width, height):
self.height = int(self.start_y + self.stem_extra_height + self.string_height + self.stem_extra_height_bottom + 1)
self.last_chord_name = None
def render_note(self, note):
mesure = self.canvas.song.mesure_at(note)
if mesure.rythm2 == 8: time1 = (note.link_start() // 144) * 144; time2 = max(note.link_end(), time1 + 144)
else: time1 = (note.link_start() // 96) * 96; time2 = max(note.link_end(), time1 + 96)
self.canvas.render_pixel(
self.canvas.time_2_x(time1) - 10.0 * self.scale,
self.y,
(time2 - time1) * self.canvas.zoom + 50.0 * self.scale,
self.height + self.canvas.default_line_height // 2 + self.canvas.default_ascent // 2,
)
def note_text(self, note): return "_"
def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous_time = -32000, next_time = 32000):
x = x + self.canvas.default_line_height // 2
canvas.PartitionDrawer.draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure, previous_time, next_time)
def draw_stem_and_beam_for_chord(self, ctx, notes, notes_y, previous, next, appo = 0):
self.draw_chord(ctx, notes, notes_y, previous, next, appo)
def draw_chord(self, ctx, notes, notes_y, previous, next, appo):
ctx.setPen(self.note_color(notes[0]))
rel_values = frozenset([note.value for note in notes])
if rel_values == frozenset([0]):
ctx.drawText(self.canvas.time_2_x(notes[0].time) + self.note_offset_x, self.y + self.start_y + 2 * self.stem_extra_height, "_")
else:
chord = self.chord_manager.identify(rel_values)
if chord: name = chord.name
else: name = "?"
mesure = self.partition.song.mesure_at(notes[0].time)
if (name != self.last_chord_name) or (mesure != self.last_chord_mesure):
max_width = 99999
if next:
next_notes = self.partition.notes_at(next.time)
rel_values = frozenset([note.value for note in next_notes])
if rel_values:
chord = self.chord_manager.identify(rel_values)
if chord and ((not self.avoid_repeat) or (chord.name != name)):
max_width = (self.canvas.time_2_x(next.time) - self.canvas.time_2_x(notes[0].time)) - 3 * self.scale
ctx.drawText(
self.canvas.time_2_x(notes[0].time) + self.note_offset_x,
self.y + self.start_y + 2 * self.stem_extra_height - self.canvas.default_ascent,
max_width,
999.0,
0,
name,
)
if self.avoid_repeat:
self.last_chord_name = name
self.last_chord_mesure = mesure
if not self.draw_strum_direction(ctx, notes):
canvas.PartitionDrawer.draw_stem_and_beam_for_chord(self, ctx, notes, notes_y, previous, next, appo)
ctx.setPen(qtcore.Qt.black)
def draw_strum_direction(self, ctx, notes, offset_x = 0.0):
return canvas.PartitionDrawer.draw_strum_direction(self, ctx, notes, offset_x + self.note_offset_x)
def draw_note(self, ctx, note, string_id, y): pass
class SingleString(object):
def __init__(self, drawer, notation_pos = -1):
self.drawer = drawer
self.notation_pos = notation_pos
base_note = 0
def text_2_value(self, note, text): return self.base_note
def value_2_text(self, note): return "/"
def __str__(self): return "Single string"
def width(self): return -1
def on_click_at(self, hole): return "0"
class TablatureChordDrawer(ChordDrawer):
ChordManager = TablatureChordManager
class PianoChordDrawer(ChordDrawer):
ChordManager = PianoChordManager
class LyreChordDrawer(ChordDrawer):
ChordManager = LyreChordManager
class AccordionChordDrawer(ChordDrawer):
avoid_repeat = 0
ChordManager = AccordionChordManager
def __init__(self, canvas_, partition, compact = False):
ChordDrawer.__init__(self, canvas_, partition, compact)
self.note_offset_x = 3.0 * self.scale
def drawers_changed(self):
try: previous = self.canvas.drawers[self.canvas.drawers.index(self) - 1]
except: self.left_hand_drawer = None
else:
if previous.__class__.__name__ == "AccordionDrawer": self.left_hand_drawer = previous
else: self.left_hand_drawer = None
if self.left_hand_drawer: self.show_header = 0
else: self.show_header = 1
def draw(self, ctx, x, y, width, height, drag = 0, draw_icon = 1):
if self.show_header:
return canvas.PartitionDrawer.draw(self, ctx, x, y, width, height, drag, 0, -34)
else:
#text_width = ctx.text_extents(_("LG"))[2]
#ctx.move_to(85.0 * self.scale - text_width, self.y + 14.5 * self.scale)
#ctx.show_text(_("LG"))
if self.compact:
ctx.drawText(65.0 * self.scale, self.y + 20.0 * self.scale, _("LG"))
return canvas.PartitionDrawer.draw(self, ctx, x, y, width, height, drag, 0, -10.0)
else:
ctx.drawText(65.0 * self.scale, self.y + 20.0 * self.scale, _("LG"))
return canvas.PartitionDrawer.draw(self, ctx, x, y, width, height, drag, 0, -20.0)
def draw_stem_and_beam_for_chord(self, ctx, notes, notes_y, previous, next, appo = 0):
self.draw_chord(ctx, notes, notes_y, previous, next, appo)
def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous_time = -32000, next_time = 32000):
pass
def on_key_press(self, event):
keyval = event.key()
if (event.modifiers() == qtcore.Qt.ShiftModifier) and (qtcore.Qt.Key_A <= keyval <= qtcore.Qt.Key_G): # A-G
value = model.NOTES[(keyval - qtcore.Qt.Key_A + 5) % 7]
if value in model.TONALITIES[self.partition.tonality]:
if model.TONALITIES[self.partition.tonality][0] == model.DIESES[0]:
value += 1
else:
value -= 1
times = {}
for note in self.canvas.selections: times[note.time] = note
do_it, undo_it = set_chord(self.canvas, self.partition, self.chord_manager, times, value, second = 0, third = 0, fourth = 0, fifth = 0, sixth = 0, seventh = 0)
UndoableOperation(do_it, undo_it, _("add bass"), self.canvas.main.undo_stack)
return 1
return ChordDrawer.on_key_press(self, event)
Songwrite3-0.1/plugins/chord/chord.py 0000664 0023421 0023421 00000035310 12675743473 017527 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2001-2011 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
# Semintone / interval
# 0 base
# 1 nine_flat
# 2 nine
# 3 three_minor
# 4 three_major
# 5 four
# 6 five_flat
# 7 five
# 8 five_sharp
# 9 six
# 10 seven_minor
# 11 seven_major
import sys
import songwrite3
import songwrite3.globdef as globdef
import songwrite3.model as model
class Chord(object):
def __init__(self, base, identified_notes, type, tonality = "C"):
self.base = base
self.identified_notes = identified_notes
self.type = type
if len(identified_notes) == 1: # Bass
self.name = "%s %s" % (songwrite3.model.note_label(base, 0, tonality, globdef.config.ENGLISH_CHORD_NAME).upper(), type)
else: # Chord
self.name = "%s %s" % (songwrite3.model.note_label(base, 0, tonality, globdef.config.ENGLISH_CHORD_NAME), type)
class ChordManager(object):
def __init__(self, partition, reverse_penality = -65):
self.partition = partition
self.reverse_penality = reverse_penality
self.caches = {}
self.default_thirds = None
def _identify_relative_notes(self, relative_notes):
if 0 in relative_notes: first = 0; relative_notes.remove(0)
else: first = None
if 4 in relative_notes: third = 4; relative_notes.remove(4) # Major
elif 3 in relative_notes: third = 3; relative_notes.remove(3) # Minor
else: third = None
if 7 in relative_notes: fifth = 7; relative_notes.remove(7) # Fifth
elif 8 in relative_notes: fifth = 8; relative_notes.remove(8) # Sharp fifth / augmented
elif 6 in relative_notes: fifth = 6; relative_notes.remove(6) # Flat fifth / diminished
else: fifth = None
seventh = None
if 10 in relative_notes: seventh = 10; relative_notes.remove(10) # Minor seventh
elif 11 in relative_notes: seventh = 11; relative_notes.remove(11) # Major seventh
elif 9 in relative_notes:
if (third == 3) and (fifth == 6): # Only with minor third and diminished fifth
seventh = 9; relative_notes.remove(9) # Diminished seventh
if 5 in relative_notes: fourth = 5; relative_notes.remove(5) # Sus4 / Eleventh
else: fourth = None
if 2 in relative_notes: second = 2; relative_notes.remove(2) # Sus2 / Nineth
elif 1 in relative_notes: second = 1; relative_notes.remove(1) # Flat nineth
else: second = None
if 9 in relative_notes: sixth = 9; relative_notes.remove(9) # Sixth
else: sixth = None
return first, second, third, fourth, fifth, sixth, seventh
def _name_identified_notes(self, base, first, second, third, fourth, fifth, sixth, seventh):
if first is None: return 0, ""
t0 = t2 = t3 = t4 = t5 = t6 = t7 = t9 = t11 = ""
lik = 0
# 3th
if third == 4: t0 = "M"; t3= ""; lik += 30
elif third == 3: t0 = "m"; t3= ""; lik += 30
else: t3 = "omit3"
# 5th
if fifth == 6: t5 = "5♭" ; lik += 10
elif fifth == 7:
lik += 50
if t3 == "omit3": t5 = "5"; t3 = ""
else: t5 = ""
elif fifth == 8: t5 = "5♯"; lik += 10
else: t5 = "omit5"
if (t0 == "M") and (t5 == "5♯"): t0 = "aug" ; t5 = ""; lik += 15
if (t0 == "m") and (t5 == "5♭" ): t0 = "dim"; t5 = ""; lik += 10
# 7th
if seventh == 9: t7 = "7dim"
elif seventh == 10: t7 = "7m" ; lik += 20
elif seventh == 11: t7 = "7M" ; lik += 10
if (t0 == "M" ) and (t7 == "7M" ): t0 = "7M" ; t7 = ""; lik += 10
elif (t0 == "m" ) and (t7 == "7m" ): t0 = "m 7" ; t7 = ""; lik += 10
elif (t0 == "M" ) and (t7 == "7m" ): t0 = "7" ; t7 = ""; lik += 10
elif (t0 == "dim") and (t7 == "7m" ): t0 = "half dim"; t7 = ""
elif (t0 == "dim") and (t7 == "7dim"): t0 = "dim 7" ; t7 = ""
elif (t0 == "m" ) and (t7 == "7M" ): t0 = "m 7M" ; t7 = ""
elif (t0 == "aug") and (t7 == "7M" ): t0 = "aug 7M" ; t7 = ""
elif (t0 == "aug") and (t7 == "7m" ): t0 = "aug 7" ; t7 = ""
elif (t0 == "" ) and (t7 == "7m" ): t0 = "7" ; t7 = ""
elif (t0 == "" ) and (t7 == "7M" ): t0 = "7M" ; t7 = ""
# 9th and 11th
if not third:
if second == 2: t2 = "sus2" ; t3 = ""; lik += 10
elif second == 1: t2 = "sus2♭"; t3 = ""; lik -= 20
if fourth == 5: t4 = "sus4" ; t3 = ""; lik += 20
else:
if second == 2: t9 = "add9" ; lik += 20
elif second == 1: t9 = "add9♭"; lik += 0
if fourth == 5: t11 = "add11"; lik += 10
if (t0 == "7M" ) and (t9 == "add9" ): t0 = "9M" ; t9 = ""
if (t0 == "m 7" ) and (t9 == "add9" ): t0 = "m 9" ; t9 = ""
if (t0 == "7" ) and (t9 == "add9" ): t0 = "9" ; t9 = ""
if (t0 == "aug 7" ) and (t9 == "add9" ): t0 = "aug 9" ; t9 = ""
if (t0 == "aug 7M") and (t9 == "add9" ): t0 = "aug 9M"; t9 = ""
if (t0 == "m 7M" ) and (t9 == "add9" ): t0 = "m 9M" ; t9 = ""
if (t0 == "7M" ) and (t11 == "add11"): t0 = "11M" ; t11 = ""; lik += 5
if (t0 == "m 7" ) and (t11 == "add11"): t0 = "m 11" ; t11 = ""; lik += 5
if (t0 == "7" ) and (t11 == "add11"): t0 = "11" ; t11 = ""; lik += 5
if (t0 == "m 7M" ) and (t11 == "add11"): t0 = "m 11M" ; t11 = ""; lik += 5
if (t0 == "9M" ) and (t11 == "add11"): t0 = "11M" ; t11 = ""; lik += 5
if (t0 == "m 9" ) and (t11 == "add11"): t0 = "m 11" ; t11 = ""; lik += 5
if (t0 == "9" ) and (t11 == "add11"): t0 = "11" ; t11 = ""; lik += 5
if (t0 == "m 9M" ) and (t11 == "add11"): t0 = "m 11M" ; t11 = ""; lik += 5
# 6th
if sixth == 9: t6 = "add6"; lik -= 20
if (t0 == "m") and (t6 == "add6"): t0 = "m 6"; t6 = ""; lik += 10
if self.default_thirds:
default = self.default_thirds.get(base % 12)
if default:
if t0 == default: t0 = ""
elif t0.startswith("%s " % default): t0 = t0[len(default) + 1:]
else:
if t0 == "M": t0 = ""
name = " ".join([_(x) for x in [t0, t2, t3, t4, t5, t6, t7, t9, t11] if x])
if name.count(_("omit")) > 1: return 0, ""
return lik, name
def identify(self, values):
if values in self.caches:
return self.caches[values]
else:
chord = self.caches[values] = self._identify(values)
return chord
def _identify(self, values):
lower_value = min(values)
best_lik = 0
best_base = None
best_name = ""
best_notes = None
for base in values:
relative_notes = set([(value - base) % 12 for value in values])
identified_notes = self._identify_relative_notes(relative_notes)
lik, name = self._name_identified_notes(base, *identified_notes)
if name is not None:
if base != lower_value:
lik += self.reverse_penality
if lik > best_lik:
best_lik = lik
best_base = base
best_name = name
best_notes = identified_notes
if best_base is None:
print("XXX pas d'accord pour", values, file = sys.stderr)
return None
return Chord(best_base, best_notes, best_name, self.partition.tonality)
def create(self, base, second = 0, third = 4, fourth = 0, fifth = 7, sixth = 0, seventh = 0):
pass
def choose_third(self, partition, base):
if ((base + 4 - model.OFFSETS[partition.tonality]) % 12) in model.NOTES: return 4
else: return 3
class TablatureChordManager(ChordManager):
def __init__(self, partition):
ChordManager.__init__(self, partition)
def create(self, base, second = 0, third = 4, fourth = 0, fifth = 7, sixth = 0, seventh = 0, barred = 0):
if barred >= 14: return None
base = base % 12
allowed_values = set([base, (base + second) % 12, (base + third) % 12, (base + fourth) % 12, (base + fifth) % 12, (base + sixth) % 12, (base + seventh) % 12])
needed_values = set(allowed_values)
values = []
frets = []
base_placed = 0
strings = list(reversed(self.partition.view.strings))
max_string_id = len(strings) - 1
for i in range(len(strings)):
if not base_placed:
value = self._relative_note_on_string(strings[i].base_note, base, barred)
fret = value - strings[i].base_note
if fret < strings[i + 1].base_note - strings[i].base_note + barred:
values.append((value, max_string_id - i))
frets .append(fret)
needed_values.discard(base)
base_placed = 1
else:
value = min([self._relative_note_on_string(strings[i].base_note, allowed_value, barred) for allowed_value in allowed_values])
values.append((value, max_string_id - i))
needed_values.discard(value % 12)
frets .append(value - strings[i].base_note)
barred = min(frets)
if needed_values or self._frets_impossible(frets, barred): return self.create(base, second, third, fourth, fifth, sixth, seventh, barred + 1)
frets = "".join([str(fret) for fret in frets])
frets = "X" * (len(strings) - len(frets)) + frets
return values, frets
def _frets_impossible(self, frets, barred = 0):
if len(frets) - frets.count(barred) > 4: return 1
if barred and (max(frets) - barred > 2): return 1
if (not barred) and (max(frets) - barred > 3): return 1
if (frets.count(barred + 3) >= 2) and (frets.count(barred + 1) >= 2): return 1
if (frets.count(barred + 4) >= 2) and (frets.count(barred + 2) >= 2): return 1
if (frets.count(barred + 1) >= 4): return 1
if (frets.count(barred + 2) >= 4): return 1
if (frets.count(barred + 3) >= 4): return 1
if (frets.count(barred + 4) >= 4): return 1
if len(frets) < 3: return 1
def _relative_note_on_string(self, string_base_note, relative_note, barred = 0):
while relative_note < string_base_note + barred: relative_note += 12
return relative_note
class PianoChordManager(ChordManager):
def __init__(self, partition):
ChordManager.__init__(self, partition)
def create(self, base, second = 0, third = 4, fourth = 0, fifth = 7, sixth = 0, seventh = 0):
base = ((base + 7) % 12) + 41
values = list(set([(base , -1),
(base + second , -1),
(base + third , -1),
(base + fourth , -1),
(base + fifth , -1),
(base + sixth , -1),
(base + seventh, -1),
(base + 12, -1),
]))
return values, ""
class AccordionChordManager(ChordManager):
def __init__(self, partition, instrument_tonality = "G/C"):
ChordManager.__init__(self, partition)
self.instrument_tonality = instrument_tonality
if instrument_tonality == "G/C": delta = 0
elif instrument_tonality == "A/D": delta = 2
self.default_thirds = {
( 0 + delta) % 12 : "M", # Do
( 2 + delta) % 12 : "M", # Ré
( 4 + delta) % 12 : "m", # Mi
( 5 + delta) % 12 : "M", # Fa
( 7 + delta) % 12 : "M", # Sol
( 9 + delta) % 12 : "m", # La
}
def _identify(self, values):
if len(values) == 1:
return Chord(tuple(values)[0], [0], "", self.partition.tonality)
else:
chord = ChordManager._identify(self, values)
words = chord.name.split(None, 1)
if len(words) == 1: chord.name = chord.name.lower()
else: chord.name = "%s %s" % (words[0].lower(), words[1])
return chord
def create(self, base, second = 0, third = 4, fourth = 0, fifth = 7, sixth = 0, seventh = 0):
if (second == 0) and (third == 0) and (fourth == 0) and (fifth == 0) and (sixth == 0) and (seventh == 0):
base = ((base + 7) % 12) + 41
values = [(base, -1)]
else:
base = ((base + 7) % 12) + 41
values = list(set([(base , -1),
(base + second , -1),
(base + third , -1),
(base + fourth , -1),
(base + fifth , -1),
(base + sixth , -1),
(base + seventh, -1),
]))
return values, ""
def choose_third(self, partition, base):
default = self.default_thirds.get(base % 12)
if default == "M": return 4
elif default == "m": return 3
if ((base + 4 - model.OFFSETS[partition.tonality]) % 12) in model.NOTES: return 4
else: return 3
return ChordManager.choose_third(self, partition, base)
class LyreChordManager(ChordManager):
def __init__(self, partition):
ChordManager.__init__(self, partition, reverse_penality = 0)
def create(self, base, second = 0, third = 4, fourth = 0, fifth = 7, sixth = 0, seventh = 0):
base = base % 12
allowed_values = set([base, (base + second) % 12, (base + third) % 12, (base + fourth) % 12, (base + fifth) % 12, (base + sixth) % 12, (base + seventh) % 12])
values = []
frets = ""
strings = list(reversed(self.partition.view.strings))
max_string_id = len(strings) - 1
for i in range(len(strings)):
if strings[i].base_note % 12 in allowed_values:
values.append((strings[i].base_note, max_string_id - i))
frets += "0"
else:
frets += "X"
return values, frets
if __name__ == "__main__":
import songwrite3, songwrite3.model, songwrite3.plugins.lyre
partition = songwrite3.model.Partition(None)
partition.set_view_type(songwrite3.model.GuitarView)
manager = TablatureChordManager(partition)
#partition.set_view_type(songwrite3.plugins.lyre.SevenStringLyreView)
#manager = LyreChordManager(partition)
#notes = frozenset([
# 36,
# 36 + 2,
# 36 + 4,
# 36 + 7,
# ])
notes = frozenset([64, 52, 60, 48, 55])
chord = manager.identify(notes)
print(chord.name)
print()
#for i in range(12):
# print songwrite3.model.note_label(i) + " \t" + manager.create(i)[1], manager.create(i, third = 3)[1], manager.create(i, seventh = 10)[1], manager.create(i, third = 3, seventh = 10)[1]
Songwrite3-0.1/plugins/chord/__init__.py 0000664 0023421 0023421 00000015654 12676234170 020166 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2011 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
#from editobj3.undoredo import *
import songwrite3.model as model
import songwrite3.plugins as plugins
from songwrite3.model import TablatureView, GuitarView, GuitarDADGADView, BassView, TablatureString
class ChordView(model.View):
view_class = "chord"
saved_chords = None
can_paste_note_by_string = 0
ok_for_lyrics = 0
def __init__(self, partition, name = ""):
super(ChordView, self).__init__(partition, name)
def _wrap_view_type(self, view_type):
class MyView(view_type):
saved_chords = None
MyView.__name__ = view_type.__name__
self._last_returned_view_type = MyView
return MyView
def get_type(self): return self._wrap_view_type(super(ChordView, self).get_type())
def change_to_view_type(self, view_type):
if issubclass(view_type, ChordView) and (not self.__class__ is view_type):
from songwrite3.plugins.chord.drawer import set_chord
new_view = view_type(self.partition)
if view_type.saved_chords: # Restore previous chord (UNDO / REDO)
view_type.saved_chords[0]()
else: # Convert chord from one chord manager to another
old_chord_manager = self .get_Drawer().ChordManager(self.partition)
new_chord_manager = view_type.get_Drawer().ChordManager(self.partition)
times = {}
do_its = []
undo_its = []
for note in self.partition.notes: times[note.time] = 1
for time in times:
notes = self.partition.notes_at(time)
rel_values = frozenset([note.value for note in notes])
chord = old_chord_manager.identify(rel_values)
v = [value or 0 for value in chord.identified_notes]
#new_chord_manager.create(chord.base, v[1], v[2], v[3], v[4], v[5], v[6])
self.partition.view = new_view # HACK : new_chord_manager may use the partition's view, e.g. for strings attribute !
do_it1, undo_it1 = set_chord(None, self.partition, new_chord_manager, [time], chord.base, v[1], v[2], v[3], v[4], v[5], v[6])
self.partition.view = self # HACK : restore view
do_its .append( do_it1)
undo_its.append(undo_it1)
previous_instrument = self.partition.instrument
def do_it():
self.partition.instrument = view_type.default_instrument
for func in do_its: func()
def undo_it():
self.partition.instrument = previous_instrument
for func in undo_its: func()
do_it()
self._last_returned_view_type.saved_chords = [undo_it]
return new_view
def get_drawer(self, canvas, compact = False): return self.get_Drawer()(canvas, self.partition, compact)
def note_string_id(self, note): return 0
class TablatureChordView(ChordView, TablatureView):
link_type = model.LINK_NOTES_DEFAULT
@classmethod
def get_Drawer(Class):
from songwrite3.plugins.chord.drawer import TablatureChordDrawer
return TablatureChordDrawer
def __xml__(self, xml = None, context = None, type = "tablature_chord"):
TablatureView.__xml__(self, xml, context, type)
class GuitarChordView(TablatureChordView, GuitarView):
link_type = model.LINK_NOTES_DEFAULT
default_instrument = 24
default_icon_filename = "guitar.png"
def __init__(self, partition, name = ""):
super(GuitarChordView, self).__init__(partition, name)
from songwrite3.plugins.lyre import *
class LyreChordView(TablatureChordView):
link_type = model.LINK_NOTES_DEFAULT
default_instrument = 46
default_icon_filename = "lyre.png"
@classmethod
def get_Drawer(Class):
from songwrite3.plugins.chord.drawer import LyreChordDrawer
return LyreChordDrawer
def __xml__(self, xml = None, context = None, type = "lyre_chord"):
TablatureView.__xml__(self, xml, context, type)
def get_type(self):
if len(self.strings) == 6: return self._wrap_view_type(model.TablatureView.get_type(self, SixStringLyreChordView))
else: return self._wrap_view_type(model.TablatureView.get_type(self, SevenStringLyreChordView))
class SixStringLyreChordView(LyreChordView, SixStringLyreView):
link_type = model.LINK_NOTES_DEFAULT
def __init__(self, partition, name = ""):
super(SixStringLyreChordView, self).__init__(partition, name)
class SevenStringLyreChordView(LyreChordView, SevenStringLyreView):
link_type = model.LINK_NOTES_DEFAULT
def __init__(self, partition, name = ""):
super(SevenStringLyreChordView, self).__init__(partition, name)
class PianoChordView(ChordView):
default_instrument = 0
default_icon_filename = "piano.png"
def __init__(self, partition, name = ""):
super(PianoChordView, self).__init__(partition, name or _("Piano"))
@classmethod
def get_Drawer(Class):
from songwrite3.plugins.chord.drawer import PianoChordDrawer
return PianoChordDrawer
def __xml__(self, xml = None, context = None):
xml.write(u'''\t\t\n")
xml.write("""\t\t\n""")
class AccordionChordView(ChordView):
default_instrument = 21
default_icon_filename = "accordion.png"
def __init__(self, partition, name = ""):
super(AccordionChordView, self).__init__(partition, name or _("Accordion"))
@classmethod
def get_Drawer(Class):
from songwrite3.plugins.chord.drawer import AccordionChordDrawer
return AccordionChordDrawer
def __xml__(self, xml = None, context = None):
xml.write(u'''\t\t\n")
xml.write("""\t\t\n""")
model.VIEW_CATEGORIES.append("chords")
plugins.ViewPlugin(TablatureChordView , TablatureString, "tablature_chord" , None)
plugins.ViewPlugin(LyreChordView , TablatureString, "lyre_chord" , None)
plugins.ViewPlugin(GuitarChordView , TablatureString, "guitar_chord" , "chords")
plugins.ViewPlugin(SixStringLyreChordView , TablatureString, "six_string_lyre_chord" , "chords")
plugins.ViewPlugin(SevenStringLyreChordView, TablatureString, "seven_string_lyre_chord", "chords")
plugins.ViewPlugin(PianoChordView , TablatureString, "piano_chord" , "chords")
plugins.ViewPlugin(AccordionChordView , TablatureString, "accordion_chord" , "chords")
Songwrite3-0.1/plugins/pdf/ 0000755 0023421 0023421 00000000000 13155312424 015502 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/pdf/pdf_canvas.py 0000664 0023421 0023421 00000026260 13062336051 020167 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, math, bisect, locale, codecs
from io import StringIO
import editobj3.editor_qt as editor_qt
import songwrite3.model as model
from songwrite3.canvas import *
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
class PDFAdditionalStaffDrawer(StaffDrawer):
def draw(self, ctx, x, y, width, height, drag = 0):
self.partition.real_view = self.partition.view
try:
self.partition.view = model.GuitarStaffView(self.partition, 0)
StaffDrawer.draw(self, ctx, x, y, width, height, drag)
finally:
self.partition.view = self.partition.real_view
del self.partition.real_view
def draw_note(self, ctx, note, string_id, y):
if (note.fx == "harmonic") and (self.partition.real_view.use_harmonics_for_octavo): # Lyre use harmonic for second octavo, not as fx
note.fx = ""
StaffDrawer.draw_note(self, ctx, note, string_id, y)
note.fx = "harmonic"
else:
StaffDrawer.draw_note(self, ctx, note, string_id, y)
class PDFLyricsDrawer(Drawer):
def __init__(self, canvas, lyrics, melody):
Drawer.__init__(self, canvas)
self.lyrics = lyrics
self.melody = melody
self.x0 = canvas.start_x + 2
self.start_y = 0
def draw(self, ctx, x, y, width, height, drag = 0):
if Drawer.draw(self, ctx, x, y, width, height, drag):
text_y = self.y + self.start_y + 1 + self.canvas.default_line_height / 2 + self.canvas.default_ascent / 2 - self.canvas.default_descent / 2
lignes = self.lyrics.text.split("\n")
nb_ligne = 1
ctx.setPen(qtcore.Qt.black)
cur_x = 0
reduced_fonts = []
font_size = self.canvas.default_font_size
while font_size > 3:
font = qtgui.QFont("Sans", font_size)
font = qtgui.QFont(font, ctx.device())
font.setPixelSize(font_size)
metrics = qtgui.QFontMetrics(font)
reduced_fonts.append((font, metrics))
font_size -= 1
def render_syllabe(syllabe, text_x, text_y, max_width):
for reduced_font, reduced_metrics in reduced_fonts:
width = reduced_metrics.width(syllabe)
if width < max_width: break
ctx.setFont(reduced_font)
ctx.drawText(text_x, text_y, syllabe)
syllabe = ""
for ligne in lignes:
syllabes = ligne.replace("\\\\", "").replace("--", "-").split("\t")
melody_notes0 = [note for note in self.melody.notes if not note.duration_fx == "appoggiatura"]
melody_notes = []
previous_time = -1000
for note in melody_notes0:
if note.time == previous_time: continue
melody_notes.append(note)
previous_time = note.time
nb_skip = 0
for i in range(min(len(melody_notes), len(syllabes))):
if nb_skip:
nb_skip -= 1
continue
note = melody_notes[i]
if not (self.canvas.time1 <= note.time < self.canvas.time2):
continue
if syllabe:
if syllabes[i] in ("_", "_-"):
max_text_width += note.duration * self.canvas.zoom
continue
else:
render_syllabe(syllabe, text_x, text_y, max_text_width)
syllabe = syllabes[i]
if not syllabe: continue
text_x = self.canvas.time_2_x(note.time) + 1
if text_x < x: continue
if text_x > x + width: break
if text_x <= cur_x: # Need breakline !
nb_ligne += 1
text_y += self.canvas.default_line_height
max_text_width = note.duration * self.canvas.zoom - 3 * self.scale
cur_x = text_x
if syllabe: render_syllabe(syllabe, text_x, text_y, max_text_width)
self.height = self.start_y + nb_ligne * self.canvas.default_line_height + 1
ctx.setFont(self.canvas.default_font)
class PDFCanvas(BaseCanvas):
is_gui_interface = 0
def __init__(self, song):
BaseCanvas.__init__(self, song)
self.set_default_font_size(song.printfontsize)
self.update_mesure_size()
def render(self, filename, x, width, time1, time2, zoom):
self.filename = filename
self.time1 = time1
self.time2 = time2
self.zoom = zoom
self.x = x
self.width = width
self.height = 10000
self.drawers = []
for i in range(2): # First and second pass for computing size
buffer = qtcore.QBuffer()
buffer.open(qtcore.QIODevice.WriteOnly)
self.draw(buffer)
self.draw(self.filename)
def set_default_font_size(self, size, device = None):
BaseCanvas.set_default_font_size(self, size, device or self.create_pdf_device(qtcore.QBuffer(), 10, 10))
# self.start_x = int((editor_qt.SMALL_ICON_SIZE + 20.0) * self.scale)
def render_all(self): pass
def create_pdf_device(self, filename, width, height):
surface = qtgui.QPdfWriter(filename)
if hasattr(qtgui, "QPageLayout"): # Qt >= 5.3
surface.setResolution(72)
page_layout = qtgui.QPageLayout()
page_layout.setMode(qtgui.QPageLayout.FullPageMode)
page_layout.setMargins(qtcore.QMarginsF(0.0, 0.0, 0.0, 0.0))
page_layout.setPageSize(qtgui.QPageSize(qtcore.QSize(min(width, height), max(width, height)), None, qtgui.QPageSize.ExactMatch))
if width > height: page_layout.setOrientation(qtgui.QPageLayout.Landscape)
else: page_layout.setOrientation(qtgui.QPageLayout.Portrait)
surface.setPageLayout(page_layout)
else: # Qt <= 5.2
surface.setMargins(qtgui.QPagedPaintDevice.Margins())
#surface.setPageSizeMM(qtcore.QSizeF(width / 72 * 25.4, height / 72 * 25.4))
surface.setPageSizeMM(qtcore.QSizeF(width / 1200.0 * 25.4, height / 1200.0 * 25.4))
return surface
def draw(self, filename):
drawer = None
surface = self.create_pdf_device(filename, self.width, self.height)
self.set_default_font_size(self.song.printfontsize, surface)
ctx = qtgui.QPainter(surface)
ctx.setRenderHints(qtgui.QPainter.Antialiasing | qtgui.QPainter.TextAntialiasing | qtgui.QPainter.SmoothPixmapTransform)
ctx.setBrush(qtcore.Qt.black)
ctx.setFont(self.default_font)
lyricss = []
def add_lyrics():
text = "\n".join([lyrics.text for lyrics in lyricss if lyrics.show_all_lines_on_melody])
if text:
cumulated_lyrics = model.Lyrics(self.song, text, _("Lyrics"))
drawer = PDFLyricsDrawer(self, cumulated_lyrics, melody)
self.drawers.append(drawer)
else:
syllabess = [line.split("\t") for lyrics in lyricss for line in lyrics.text.split("\n") if not lyrics.show_all_lines_on_melody]
if syllabess:
cummulated_syllabes = [""] * len([note for note in melody.notes if not note.duration_fx == "appoggiatura"])
for i in range(len(cummulated_syllabes)):
for syllabes in syllabess:
if (i < len(syllabes)) and syllabes[i]:
cummulated_syllabes[i] = syllabes[i]
break
cumulated_lyrics = model.Lyrics(self.song, "\t".join(cummulated_syllabes), _("Lyrics"))
drawer = PDFLyricsDrawer(self, cumulated_lyrics, melody)
self.drawers.append(drawer)
lyricss.__imul__(0)
if not self.drawers:
for partition in self.song.partitions:
if isinstance(partition, model.Partition):
add_lyrics()
melody = partition
if not partition.notes_at(self.time1, self.time2 - 1): continue
if getattr(partition, "print_with_staff_too", 0) and not isinstance(partition.view, model.StaffView):
drawer = PDFAdditionalStaffDrawer(self, partition, compact = 1)
self.drawers.append(drawer)
if isinstance(partition, model.Lyrics): lyricss.append(partition)
else: drawer = partition.view.get_drawer(self, compact = 1)
if drawer:
if getattr(partition, "print_with_staff_too", 0): drawer.show_header = 0
self.drawers.append(drawer)
drawer = None
add_lyrics()
for drawer in self.drawers: drawer.drawers_changed()
if not self.drawers:
for partition in self.song.partitions:
if isinstance(partition, model.Partition):
add_lyrics()
melody = partition
if getattr(partition, "print_with_staff_too", 0) and not isinstance(partition.view, model.StaffView):
drawer = PDFAdditionalStaffDrawer(self, partition, compact = 1)
self.drawers.append(drawer)
if isinstance(partition, model.Lyrics): lyricss.append(partition)
else: drawer = partition.view.get_drawer(self, compact = 1)
if drawer:
if getattr(partition, "print_with_staff_too", 0): drawer.show_header = 0
self.drawers.append(drawer)
drawer = None
break # Only first
add_lyrics()
for drawer in self.drawers: drawer.drawers_changed()
x = self.start_x
width = self.width
y = 0
y1 = 0
y2 = 0
dy = 0
total_height = 0
for drawer in self.drawers:
drawer.y = dy
drawer.draw(ctx, x, y, width, self.height)
dy += drawer.height
total_height += drawer.height
# Show mesure number at the beginning
if self.drawers and isinstance(self.drawers[0], PartitionDrawer):
mesure_number = str(1 + self.song.mesures.index(self.song.mesure_at(self.time1)))
ctx.setFont(self.small_font)
text_size = ctx.fontMetrics().size(0, mesure_number)
ctx.drawText(
self.start_x - text_size.width() // 2,
self.drawers[0].y + self.drawers[0].start_y + self.drawers[0].stem_extra_height - text_size.height() // 2,
mesure_number)
for drawer in self.drawers:
if hasattr(drawer, "strings") and (len(drawer.strings) > 1):
y1 = drawer.y + drawer.start_y + drawer.stem_extra_height + drawer.string_height * 1.5
break
for drawer in reversed(self.drawers):
if hasattr(drawer, "strings") and (len(drawer.strings) > 1):
y2 = drawer.y + drawer.start_y + drawer.stem_extra_height + drawer.string_height * 1.5
break
mesure = self.song.mesure_at(self.time1)
x = self.start_x
#x = self.time_2_x(self.time1)
#x = self.start_x - self.x + self.time1 * self.zoom
ctx.drawLine(x, y1, x, y2)
ctx.end()
self.height = total_height
Songwrite3-0.1/plugins/pdf/pdf_latex.py 0000664 0023421 0023421 00000027653 13062336063 020043 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2001-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, os, tempfile, shutil
from io import StringIO
import songwrite3.globdef as globdef
import songwrite3.model as model
import songwrite3.plugins as plugins
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
def escape_latex(s):
return s.replace("°", "\\symbol{6}").replace("#", "\\#").replace("_", "\\_").replace("~", "\\~").replace("&", "\\&").replace("%", "\\%").replace("$", "\\$").replace("^", "\\^")
lang_iso2latex = { # Languages supported by LaTeX with Babel
"" : "\\usepackage[]{babel}",
}
lang_iso2latex = { # Languages supported by LaTeX with Babel
"ar" : "arabic",
"hy" : "armenian",
"eu" : "basque",
"bg" : "bulgarian",
"ca" : "catalan",
"hr" : "croatian",
"cs" : "czech",
"da" : "danish",
"nl" : "dutch",
"en" : "english",
"eo" : "esperanto",
"et" : "estonian",
"fa" : "farsi",
"fi" : "finnish",
"fr" : "french",
"gl" : "galician",
"de" : "german",
"el" : "greek",
"hu" : "hungarian",
"is" : "icelandic",
"as" : "assamese",
"bn" : "bengali",
"gu" : "gujarati",
"hi" : "hindi",
"kn" : "kannada",
"ml" : "malayalam",
"mr" : "marathi",
"or" : "oriya",
"pa" : "panjabi",
"ta" : "tamil",
"te" : "telugu",
"id" : "indonesian",
"ia" : "interlingua",
"ga" : "irish",
"it" : "italian",
"ku" : "kurmanji",
"lo" : "lao",
"la" : "latin",
"lv" : "latvian",
"lt" : "lithuanian",
"mn" : "mongolian",
"nb" : "bokmal",
"nn" : "nynorsk",
"pl" : "polish",
"pt" : "portuguese",
"ro" : "romanian",
"ru" : "russian",
"sa" : "sanskrit",
"sr" : "serbian",
"sk" : "slovak",
"sl" : "slovenian",
"es" : "spanish",
"sv" : "swedish",
"tr" : "turkish",
"tk" : "turkmen",
"uk" : "ukrainian",
"cy" : "welsh",
"--" : "",
}
def latexify_songbook(tmp_dir, songbook):
latexes = []
lang = None
for song_ref in songbook.song_refs:
song = song_ref.get_song()
latexes.append(latexify_song(tmp_dir, song, 1))
if not lang: lang = song.lang
lang_latex = lang_iso2latex[lang]
babel_code = "\\usepackage[%s]{babel}" % lang_latex
open(os.path.join(tmp_dir, "main.latex"), "w").write(("""
\\documentclass[%s,twoside,10pt]{article}
\\usepackage[T1]{fontenc}
\\usepackage[utf8]{inputenc}
\\usepackage[lmargin=%scm,rmargin=%scm,tmargin=%scm,bmargin=%scm]{geometry}
\\usepackage{graphicx}
\\usepackage{lmodern,aecompl}
\\usepackage{multicol}
%s
\\begin{document}
\\sloppy
\\title {%s}
\\author{%s}
\\date {}
\\maketitle
\\vfill
%s
\\begin{multicols}{2}
\\tableofcontents
\\end{multicols}
\\pagebreak
%s
\\end{document}
""" % (globdef.config.PAGE_FORMAT,
globdef.config.PAGE_MARGIN_INTERIOR,
globdef.config.PAGE_MARGIN_EXTERIOR,
globdef.config.PAGE_MARGIN_TOP,
globdef.config.PAGE_MARGIN_BOTTOM,
babel_code,
escape_latex(songbook.title),
escape_latex(songbook.authors),
escape_latex(songbook.comments),
"""
\\newpage
""".join(latexes),
)))
PDF_ID = 0
def latexify_song(tmp_dir, song, songbook = 0):
global PDF_ID
from songwrite3.plugins.pdf.pdf_canvas import BaseCanvas, PDFCanvas
song.remove_unused_mesures()
latex = StringIO()
partitions = song.partitions
lang = lang_iso2latex.get(song.lang, None)
if lang: babel_code = "\\usepackage[%s]{babel}" % lang
else: babel_code = ""
if songbook:
latex.write("""
\\addcontentsline{toc}{subsection}{%s}
""" % escape_latex(song.title))
else:
latex.write("""
\\documentclass[%s,twoside,10pt]{article}
\\usepackage[T1]{fontenc}
\\usepackage[utf8]{inputenc}
\\usepackage[lmargin=%scm,rmargin=%scm,tmargin=%scm,bmargin=%scm,headheight=0cm,headsep=0cm]{geometry}
\\usepackage{graphicx}
\\usepackage{aecompl}""" % (globdef.config.PAGE_FORMAT,
globdef.config.PAGE_MARGIN_INTERIOR,
globdef.config.PAGE_MARGIN_EXTERIOR,
globdef.config.PAGE_MARGIN_TOP,
globdef.config.PAGE_MARGIN_BOTTOM,
))
if song.print_lyrics_columns > 1: latex.write("""\\usepackage{multicol}""")
latex.write("""
%s
\\setlength{\\topskip}{0pt}
\\begin{document}
\\sloppy
""" % babel_code)
if song.title:
latex.write("""
\\begin{center}\\begin{LARGE} %s \\end{LARGE}\\end{center}
""" % escape_latex(song.title))
date = song.date()
if song.authors or date:
if not date: authors_date = song.authors
elif not song.authors: authors_date = date
else: authors_date = "%s (%s)" % (song.authors, date)
latex.write("""
\\begin{center}\\begin{large} %s \\end{large}\\end{center}
""" % escape_latex(authors_date))
if song.comments:
latex.write("""
%s
""" % escape_latex(song.comments).replace("\\n", "\\n\\n"))
max_time = max([10] + [partition.end_time() for partition in song.partitions if isinstance(partition, model.Partition)])
max_width = {
"a3paper" : 29.7,
"a4paper" : 21.0,
"a5paper" : 14.8,
"letterpaper": 21.6,
"legalpaper" : 21.6,
}[globdef.config.PAGE_FORMAT] - (globdef.config.PAGE_MARGIN_INTERIOR + globdef.config.PAGE_MARGIN_EXTERIOR)
max_width = int(round(max_width * 56.8421052631579))
max_mesures_width = 0
mesuress = []
selected_mesures = []
canvas = PDFCanvas(song)
#canvas.set_default_font_size(song.printfontsize)
canvas.update_mesure_size()
width = canvas.start_x
zoom = (max_width - canvas.start_x - 1) / float(song.mesures[min(song.print_nb_mesures_per_line, len(song.mesures)) - 1].end_time() )
for mesure in song.mesures:
if len(selected_mesures) >= song.print_nb_mesures_per_line:
mesuress.append(selected_mesures)
selected_mesures = []
selected_mesures.append(mesure)
if selected_mesures:
if mesuress and (len(selected_mesures) == 1) and (song.print_nb_mesures_per_line > 3):
mesuress[-1].append(selected_mesures[0])
else:
mesuress.append(selected_mesures)
def mesure_2_repeat(mesure):
start_repeat = end_repeat = 0
for symbol in song.playlist.symbols.get(mesure) or []:
if symbol.startswith(r"\repeat"): start_repeat = 1
elif symbol == r"} % end alternative": end_repeat = 1
elif symbol == r"} % end repeat": end_repeat = 1
return start_repeat, end_repeat
def mesures_2_x_width_zoom(mesures, is_last, previous_mesures, previous_zoom):
if mesures[0] is song.mesures[0]:
x = 0
dx = 0
start_repeat, end_repeat = mesure_2_repeat(mesures[0])
if start_repeat: x +=0.3 * canvas.scale; dx -=0.3 * canvas.scale
else:
x = canvas.time_2_x(mesures[0].time) - canvas.start_x
dx = x - mesures[ 0].time
start_repeat, end_repeat = mesure_2_repeat(mesures[0])
if start_repeat and end_repeat: x -= 17 * canvas.scale; dx -= 17 * canvas.scale
elif start_repeat: x -= 16 * canvas.scale; dx -= 16 * canvas.scale
elif end_repeat: x -= 2 * canvas.scale; dx -= 2 * canvas.scale
else: x -= 2 * canvas.scale; dx -= 2 * canvas.scale
x2 = canvas.time_2_x(mesures[-1].end_time()) - canvas.start_x
dx2 = x2 - mesures[-1].end_time()
try: next_mesure = song.mesures[mesures[-1].get_number() + 1]
except: next_mesure = None
start_repeat, end_repeat = mesure_2_repeat(next_mesure)
if start_repeat and end_repeat: x2 -= 15 * canvas.scale; dx2 -= 15 * canvas.scale
elif start_repeat: x2 -= 16 * canvas.scale; dx2 -= 16 * canvas.scale
elif end_repeat: x2 -= 2 * canvas.scale; dx2 -= 2 * canvas.scale
else: x2 -=1.1 * canvas.scale; dx2 -=1.1 * canvas.scale
if is_last and not (len(mesures) < len(previous_mesures)):
x2 += 5 * canvas.scale; dx2 += 5 * canvas.scale
width = x2 - x
if is_last and (len(mesures) < len(previous_mesures)):
zoom = previous_zoom
width = (dx2 - dx) + (width - (dx2 - dx)) * zoom + canvas.start_x
if not end_repeat: width += 5 * canvas.scale
else:
zoom = (max_width - canvas.start_x - (dx2 - dx)) / float(width - (dx2 - dx))
width = max_width
x = dx + (x - dx) * zoom
return x, width, zoom
# We need a second canvas; the first one is only for computation
pdf_canvas = PDFCanvas(song)
latex.write("""
\\begin{flushleft}
""")
previous_mesures = []
previous_zoom = 0.0
for mesures in mesuress:
x, width, zoom = mesures_2_x_width_zoom(mesures, mesures is mesuress[-1], previous_mesures, previous_zoom)
pdf_filename = os.path.join(tmp_dir, "pdf_file_%s.pdf" % PDF_ID)
pdf_canvas.render(pdf_filename, x, width, mesures[0].time, mesures[-1].end_time(), zoom)
previous_mesures = mesures
previous_zoom = zoom
if hasattr(qtgui, "QPageLayout"): # setResolution is used for changing resolution.
latex.write("""
\\noindent\\includegraphics[clip,scale=0.5]{pdf_file_%s.pdf}\\par""" % PDF_ID)
else:
latex.write("""
\\noindent\\includegraphics[clip,scale=8.3333]{pdf_file_%s.pdf}\\par""" % PDF_ID)
if mesures is mesuress[-1]: latex.write("""\n""")
else: latex.write("""\\bigskip\n""")
PDF_ID += 1
latex.write("""
\\end{flushleft}
""")
latex.write("""
\\vfill
""")
if song.print_lyrics_columns > 1:
latex.write("""
\\begin{multicols}{%s}
""" % song.print_lyrics_columns)
first = 1
for partition in partitions:
if isinstance(partition, model.Lyrics):
if not partition.show_all_lines_after_score: continue
if first:
first = 0
#latex.write("""\\noindent """)
else:
#if song.print_lyrics_columns > 1:
latex.write("""
\\smallskip{}
""")
#else:
# latex.write("""\\\\
#\\noindent """)
if partition.header:
latex.write("""\\textbf{%s}\\nopagebreak[4]\\\\%%
""" % escape_latex(partition.header))
else:
latex.write("""\\noindent """)
text = partition.text.replace("_-\t", "").replace("_\t", " ").replace("-\t", "").replace("\t", " ").replace("_", "").replace(" ,", ",").replace(" ,", ",")
if text.endswith("\\"): text = text[:-2] # Remove last breakline
latex.write(escape_latex(text))
if song.print_lyrics_columns > 1:
latex.write("""
\end{multicols}
""")
# else:
# latex.write("""
#\\vfill
#""")
if not songbook:
latex.write("""
\\end{document}
""")
if songbook:
return latex.getvalue()
else:
open(os.path.join(tmp_dir, "main.latex"), "w").write(latex.getvalue())
PDF_PAGE_FORMATS = {
"a3paper" : "a3",
"a4paper" : "a4",
"a5paper" : "a5",
"letterpaper" : "letter",
"legalpaper" : "legal",
}
def do(command):
print(file = sys.stderr)
print("Running '%s'..." % command, file = sys.stderr)
os.system(command)
def pdfy(song):
tmp_dir = tempfile.mkdtemp()
print(file = sys.stderr)
print("Using temporary directory '%s'..." % tmp_dir, file = sys.stderr)
if isinstance(song, model.Songbook):
latexify_songbook(tmp_dir, song)
else:
latexify_song (tmp_dir, song)
do("cd %s; pdflatex -interaction=nonstopmode main.latex" % tmp_dir)
do("cd %s; pdflatex -interaction=nonstopmode main.latex" % tmp_dir) # For table of content
with open(os.path.join(tmp_dir, "main.pdf"), "rb") as f: r = f.read()
shutil.rmtree(tmp_dir)
return r
Songwrite3-0.1/plugins/pdf/__init__.py 0000664 0023421 0023421 00000004273 12727241703 017630 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, os, atexit
import songwrite3.plugins as plugins
import songwrite3.model as model
import songwrite3.globdef as globdef
PREVIEW_TMP_FILE = ""
def remove_preview_tmp_file():
global PREVIEW_TMP_FILE
if PREVIEW_TMP_FILE:
os.unlink(PREVIEW_TMP_FILE)
PREVIEW_TMP_FILE = ""
atexit.register(remove_preview_tmp_file)
class ExportPluginPDF(plugins.ExportPlugin):
def __init__(self):
plugins.ExportPlugin.__init__(self, "PDF", [".pdf"], 1, 1)
def export_to_string(self, song):
import songwrite3.plugins.pdf.pdf_latex as pdf_latex
return pdf_latex.pdfy(song)
def print_preview(self, song):
import songwrite3.plugins.pdf.pdf_latex as pdf_latex
global PREVIEW_TMP_FILE
if PREVIEW_TMP_FILE: remove_preview_tmp_file()
pdf = pdf_latex.pdfy(song)
import subprocess
pdf_command = globdef.config.get_preview_command_pdf()
if "%s" in pdf_command:
import tempfile
fid, PREVIEW_TMP_FILE = tempfile.mkstemp(suffix = ".pdf", text = 0)
with open(PREVIEW_TMP_FILE, "wb") as f: f.write(pdf)
command = pdf_command % PREVIEW_TMP_FILE
print("Running '%s'" % command, file = sys.stderr)
p = subprocess.Popen(command, shell = True, close_fds = True)
else:
print("Running '%s'" % pdf_command, file = sys.stderr)
p = subprocess.Popen(pdf_command, shell = True, stdin = subprocess.PIPE, close_fds = True)
p.stdin.write(pdf)
p.stdin.close()
ExportPluginPDF()
Songwrite3-0.1/plugins/accordion/ 0000755 0023421 0023421 00000000000 13155312423 016671 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/accordion/drawer.py 0000664 0023421 0023421 00000015612 12676244202 020544 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2011 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import editobj3
from editobj3.undoredo import *
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
import songwrite3.model as model
import songwrite3.globdef as globdef
import songwrite3.canvas as canvas
import songwrite3.player as player
from songwrite3.plugins.chord.chord import AccordionChordManager
class AccordionDrawer(canvas.TablatureDrawer):
def __init__(self, canvas_, partition, compact = False):
canvas.TablatureDrawer.__init__(self, canvas_, partition, compact)
partition.g8 = 0
self.chord_manager = AccordionChordManager(partition)
self.stem_extra_height_bottom = 0
def drawers_changed(self):
try: next = self.canvas.drawers[self.canvas.drawers.index(self) + 1]
except: self.right_hand_drawer = None
else:
if next.__class__.__name__ == "AccordionChordDrawer": self.right_hand_drawer = next
else: self.right_hand_drawer = None
def create_cursor(self, time, duration, string_id, value):
cursor = canvas.PartitionDrawer.create_cursor(self, time, duration, string_id, value)
if string_id == 0: cursor.bellows_dir = -1
else: cursor.bellows_dir = 1
return cursor
def on_key_press(self, event):
if event.key() == qtcore.Qt.Key_Apostrophe: # '
self.toggle_button_rank()
return 1
def toggle_button_rank(self):
if not self.canvas.selections: return
notes = list(self.canvas.selections)
old_values = {}
for note in notes: old_values[note] = (note.value, note.button_rank)
text = self.partition.view.strings[self.partition.view.note_string_id(notes[0])].value_2_text(notes[0])
if "'" in text: new_button_rank = 0
else: new_button_rank = 1
first = [1]
def do_it():
for note in notes:
string = self.partition.view.strings[self.partition.view.note_string_id(note)]
text = string.value_2_text(note)
if ( "'" in text) and (new_button_rank == 0):
note.value = string.text_2_value(note, text[:-1])
elif (not "'" in text) and (new_button_rank == 1):
note.value = string.text_2_value(note, text + "'")
if first[0]:
first[0] = 0
player.play_note(note.partition.instrument, note.value)
def undo_it():
for note, (value, button_rank) in old_values.items():
note.value = value
if not button_rank is None: note.button_rank = button_rank
UndoableOperation(do_it, undo_it, _("change button rank"), self.canvas.main.undo_stack)
def note_value_possible(self, note, value):
return value in self.partition.view.strings[self.partition.view.note_string_id(note)].value2buttons
def draw(self, ctx, x, y, width, height, drag = 0, draw_icon = 1):
canvas.TablatureDrawer.draw(self, ctx, x, y, width, height, drag, draw_icon)
def draw_mesure(self, ctx, time, mesure, y, height):
canvas.TablatureDrawer.draw_mesure(self, ctx, time, mesure, y - self.string_height // 2 - 1, height + self.string_height)
def draw_strings(self, ctx, x, y, width, height):
string_x = x - 25.0 * self.scale
string_x2 = x + width
if (string_x < string_x2):
#ctx.set_source_rgb(0.0, 0.0, 0.0)
#ctx.set_line_width(1.0)
for i in range(len(self.strings) + 1):
string_y = self.string_id_2_y(i) - self.string_height // 2
if i == 0: string_y0 = string_y
if y - 2 < string_y < y + height + 2:
string_y -= 0.5
#ctx.move_to(string_x, string_y)
#ctx.line_to(string_x2, string_y)
#ctx.stroke()
self._draw_perfect_line(ctx, string_x, string_y, string_x2, string_y)
#ctx.move_to(string_x - 0.5, string_y + 0.5)
#ctx.line_to(string_x - 0.5, string_y0 - 1)
#ctx.stroke()
#self._draw_perfect_line(ctx, string_x - 0.5, string_y + 0.5, string_x - 0.5, string_y0 - 1)
self._draw_perfect_line(ctx, string_x, string_y, string_x, string_y0)
#ctx.move_to(string_x + 8.0 * self.scale, string_y0 + self.string_height * 0.75)
#ctx.show_text(_("__accordion__push"))
#ctx.move_to(string_x + 8.0 * self.scale, string_y0 + self.string_height * 1.75)
#ctx.show_text(_("__accordion__draw"))
ctx.drawText(string_x + 8.0 * self.scale, string_y0 + self.string_height * 0.85, _("__accordion__push"))
ctx.drawText(string_x + 8.0 * self.scale, string_y0 + self.string_height * 1.85, _("__accordion__draw"))
def draw_note(self, ctx, note, string_id, y):
if self.right_hand_drawer:
right_hand_notes = self.right_hand_drawer.partition.notes_before(note.time + 1)
while right_hand_notes:
p = right_hand_notes[-1].next()
if p and (p.time < note.end_time()):
right_hand_notes.append(p)
else: break
times = {}
for right_hand_note in right_hand_notes:
if right_hand_note.end_time() > note.time:
if right_hand_note.time in times: times[right_hand_note.time].append(right_hand_note)
else: times[right_hand_note.time] = [right_hand_note]
ok = 1
for time, right_hand_notes in times.items():
if len(right_hand_notes) > 1: # chord
rel_values = frozenset([right_hand_note.value for right_hand_note in right_hand_notes])
chord = self.right_hand_drawer.chord_manager.identify(rel_values)
right_value = chord.base % 12
else: # bass
right_value = right_hand_notes[0].value % 12
required_bellow_dir = CHORD_BELLOWS_DIR.get(right_value, 0)
if required_bellow_dir and (note.bellows_dir != required_bellow_dir):
x = self.canvas.time_2_x(note.time)
y += self.note_offset_y
ctx.setPen(qtcore.Qt.red)
ctx.drawText(x + 3 * self.scale, y + self.canvas.default_ascent / 2 - self.canvas.default_descent / 2, "X")
break
canvas.PartitionDrawer.draw_note(self, ctx, note, string_id, y)
draw_note_fx_link = canvas.PartitionDrawer.draw_note_fx_link
CHORD_BELLOWS_DIR = {
0 : -1, # Do
2 : 1, # Ré
4 : -1, # Mi
5 : 0, # Fa
7 : 0, # Sol
9 : 1, # La
}
Songwrite3-0.1/plugins/accordion/__init__.py 0000664 0023421 0023421 00000011771 13014114710 021004 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2011 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import editobj3, editobj3.introsp as introsp, editobj3.field as field
import songwrite3.model as model
import songwrite3.plugins as plugins
#model.Note.accompaniment = None
model.Note.button_rank = None
model.Note.bellows_dir = None # -1 Push, 1 draw
model.NOTE_ATTRS.append("button_rank")
model.NOTE_ATTRS.append("bellows_dir")
accordion_tonalities = {
-5 : _("C/F"),
0 : _("G/C"),
2 : _("A/D"),
}
class AccordionView(model.View):
view_class = "accordion"
default_icon_filename = "accordion.png"
default_instrument = 21
link_type = model.LINK_NOTES_DEFAULT
can_paste_note_by_string = 1
automatic_string_id = 1
def __init__(self, partition, name = ""):
model.View.__init__(self, partition, name or _("Accordion"))
if partition:
if not hasattr(partition, "print_with_staff_too"): partition.print_with_staff_too = 0
if not hasattr(partition, "accordion_tonalities"): partition.accordion_tonalities = 0
self.strings = [
AccordionLeftHandString (self, -1, { "1" : 51, "2" : 50, "3" : 55, "4" : 59, "5" : 62, "6" : 67, "7" : 71, "8" : 74, "9" : 79, "10" : 83, "11" : 86,
"1'": 56, "2'": 55, "3'": 60, "4'": 64, "5'": 67, "6'": 72, "7'": 76, "8'": 79, "9'": 84, "10'": 88 }), # Poussé
AccordionLeftHandString (self, 1, { "1" : 49, "2" : 54, "3" : 57, "4" : 60, "5" : 64, "6" : 66, "7" : 69, "8" : 72, "9" : 76, "10" : 78, "11" : 81,
"1'": 48, "2'": 59, "3'": 62, "4'": 65, "5'": 69, "6'": 71, "7'": 74, "8'": 77, "9'": 81, "10'": 83}), # Tiré
]
def note_string_id(self, note):
if note.bellows_dir == -1: return 0
elif note.bellows_dir == 1: return 1
return 1
def get_drawer(self, canvas, compact = False):
from songwrite3.plugins.accordion.drawer import AccordionDrawer
return AccordionDrawer(canvas, self.partition, compact)
def get_icon_filename(self): return "accordion.png"
def __xml__(self, xml = None, context = None):
xml.write('''\t\t\n''')
xml.write("""\t\t\n""")
#class AccordionLeftHandString(object):
class AccordionLeftHandString(model.TablatureString):
def __init__(self, view, bellows_dir, button2value):
self.view = view
self.bellows_dir = bellows_dir
self.button2value = button2value
self.value2buttons = {}
# First pass for button rank 0, then second pass for button rank 1
for button, value in button2value.items():
if not "'" in button:
if value in self.value2buttons: self.value2buttons[value].append(button)
else: self.value2buttons[value] = [button]
for button, value in button2value.items():
if "'" in button:
if value in self.value2buttons: self.value2buttons[value].append(button)
else: self.value2buttons[value] = [button]
self.base_note = self.button2value["3"]
def text_2_value(self, note, text):
value = self.button2value.get(text)
if value is None:
if len(text) > 1:
value = self.button2value.get(text[-1])
if value is None:
value = self.button2value["3"]
note.button_rank = text.count("'")
note.bellows_dir = self.bellows_dir
return value
def value_2_text(self, note, change_string = 1):
buttons = self.value2buttons.get(note.value)
if buttons is None:
if change_string:
for string in self.view.strings:
if string is self: continue
text = string.value_2_text(note, change_string = 0)
if text:
note.bellows_dir = string.bellows_dir
return text
return "?"
if len(buttons) == 1: return buttons[0]
button_rank = note.button_rank
if button_rank is None:
if (note.partition.tonality == "G") or (note.partition.tonality == "D"): button_rank = 0
else: button_rank = 1
return buttons[button_rank]
def width(self): return 7
#def __str__(self): return _(self.__class__.__name__) % model.note_label(self.base_note)
#descr = introsp.description(AccordionLeftHandString)
introsp.def_attr("bellows_dir", field.HiddenField)
plugins.ViewPlugin(AccordionView, None, "accordion", "tab")
Songwrite3-0.1/plugins/abc/ 0000755 0023421 0023421 00000000000 13155312423 015455 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/abc/abc.py 0000664 0023421 0023421 00000053746 12702462512 016577 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# SongWrite
# Copyright (C) 2004 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
# Ref : http://anamnese.online.fr/abc/guide_abc.txt
import cStringIO, string, re
import songwrite3.globdef as globdef
import songwrite3.model as model
_tabnotes = "abcdefghiklmnopqrstuvwxyz"
_notes = {
"C" : ( "c", "^c", "d", "^d", "e", "f", "f", "g", "^g", "a", "^a", "b"),
"G" : ( "c", "^c", "d", "^d", "e", "_f", "f", "g", "^g", "a", "^a", "b"),
"D" : ("_c", "c", "d", "^d", "e", "_f", "f", "g", "^g", "a", "^a", "b"),
"A" : ("_c", "c", "d", "^d", "e", "_f", "f", "_g", "g", "a", "^a", "b"),
"E" : ("_c", "c", "_d", "d", "e", "_f", "f", "_g", "g", "a", "^a", "b"),
"B" : ("_c", "c", "_d", "d", "e", "_f", "f", "_g", "g", "_a", "a", "b"),
"F#": ("_c", "c", "_d", "d", "_e", "e", "f", "_g", "g", "_a", "a", "b"),
"C#": ( "b", "c", "_d", "d", "_e", "e", "f", "_g", "g", "_a", "a", "_b"),
"F" : ( "c", "^c", "d", "^d", "e", "f", "^f", "g", "^g", "a", "b", "^b"),
"Bb": ( "c", "^c", "d", "e", "^e", "f", "^f", "g", "^g", "a", "b", "^b"),
"Eb": ( "c", "^c", "d", "e", "^e", "f", "^f", "g", "a", "^a", "b", "^b"),
"Ab": ( "c", "d", "^d", "e", "^e", "f", "^f", "g", "a", "^a", "b", "^b"),
"Db": ( "c", "d", "^d", "e", "^e", "f", "g", "^g", "a", "^a", "b", "^b"),
"Gb": ("^c", "d", "^d", "e", "^e", "f", "g", "^g", "a", "^a", "b", "c"),
"Cb": ("^c", "d", "^d", "e", "f", "^f", "g", "^g", "a", "^a", "b", "c"),
}
_duration_2_abc = {
# Normal duration
768 : "8",
384 : "4",
192 : "2",
96 : "1",
48 : "/2",
24 : "/4",
12 : "/8",
6 : "/16",
3 : "/32",
# Doted durations
576 : "6",
288 : "3",
144 : "3/2",
72 : "3/4",
36 : "3/8",
18 : "3/16",
9 : "3/32",
# Triplets
32 : "1/3",
16 : "1/6",
8 : "1/12",
4 : "1/24",
}
_abc_2_duration = dict([(abc, duration) for (duration, abc) in _duration_2_abc.items()])
_abc_2_duration["/"] = _abc_2_duration["/2"]
def duration_2_abc(duration, note = None):
if note and note.is_start_triplet(): return "(3" + _duration_2_abc[duration]
return _duration_2_abc[duration]
def abc_2_duration(abc):
if abc.startswith(u"1/"): abc = abc[1:]
return _abc_2_duration[abc] #* default_duration // 96
_abc_2_value = {
"C" : { "C" : 60, "D" : 62, "E" : 64, "F" : 65, "G" : 67, "A" : 69, "B" : 71 },
"G" : { "C" : 60, "D" : 62, "E" : 64, "F" : 66, "G" : 67, "A" : 69, "B" : 71 },
"D" : { "C" : 61, "D" : 62, "E" : 64, "F" : 66, "G" : 67, "A" : 69, "B" : 71 },
"A" : { "C" : 61, "D" : 62, "E" : 64, "F" : 66, "G" : 68, "A" : 69, "B" : 71 },
"E" : { "C" : 61, "D" : 63, "E" : 64, "F" : 66, "G" : 68, "A" : 69, "B" : 71 },
"B" : { "C" : 61, "D" : 63, "E" : 64, "F" : 66, "G" : 68, "A" : 70, "B" : 71 },
"F#": { "C" : 61, "D" : 63, "E" : 65, "F" : 66, "G" : 68, "A" : 70, "B" : 71 },
"C#": { "C" : 61, "D" : 63, "E" : 65, "F" : 66, "G" : 68, "A" : 70, "B" : 72 },
"F" : { "C" : 60, "D" : 62, "E" : 64, "F" : 65, "G" : 67, "A" : 69, "B" : 70 },
"Bb": { "C" : 60, "D" : 62, "E" : 63, "F" : 65, "G" : 67, "A" : 69, "B" : 70 },
"Eb": { "C" : 60, "D" : 62, "E" : 63, "F" : 65, "G" : 67, "A" : 68, "B" : 70 },
"Ab": { "C" : 60, "D" : 61, "E" : 63, "F" : 65, "G" : 67, "A" : 68, "B" : 70 },
"Db": { "C" : 60, "D" : 61, "E" : 63, "F" : 65, "G" : 66, "A" : 68, "B" : 70 },
"Gb": { "C" : 59, "D" : 61, "E" : 63, "F" : 65, "G" : 66, "A" : 68, "B" : 70 },
"Cb": { "C" : 59, "D" : 61, "E" : 63, "F" : 64, "G" : 66, "A" : 68, "B" : 70 },
}
def abc_2_value(abc, tonality):
value = 0
if abc in "abcdefg":
abc = abc.upper()
value += 12
value += _abc_2_value[tonality][abc]
return value
def parse(abc):
if not isinstance(abc, str): abc = abc.decode("utf8")
# Simplify bars placed at breakline
abc = abc.replace("\r", "\n")
abc = re.sub(u"\\|\\|", u"|", abc)
abc = re.sub(u"\\|\\s+\\|", u"|", abc)
abc = re.sub(u":\\|:", u"::", abc)
song = model.Song()
partition = model.Partition(song, model.PianoView.default_instrument, view_type = model.PianoView)
song.add_partition(partition)
song.mesures = []
titles = []
comments = []
tempo = 90
part_start = -1
rhythm1 = 4
rhythm2 = 4
in_header = 1
time = 0
syncope = 0
note_value_offset = 0
current_duration = 0
duration_multiplicator = 1
in_tripplet = 0
ornementation = 0
in_link = 0
start_repeat = None
def last_mesure_end_time():
if song.mesures: return song.mesures[-1].end_time()
return 0
def mesure_ended():
if song.mesures:
mesure = song.mesures[-1]
if (len(song.mesures) == 1) or (mesure is start_repeat):
#delta_t = mesure.end_time() - partition.notes[-1].end_time()
delta_t = mesure.end_time() - (time + current_duration)
if delta_t > 0:
for note in reversed(partition.notes):
if note.time < mesure.time: break
note.time += delta_t
lines = abc.split(u"\n")
for line in lines:
if not line: continue
if "%" in line: line = line.split("%")[0] # ABC Comment
if (len(line) > 1) and (line[1] == u":") and (line[0] in string.uppercase):
attr = line[0]
value = line[2:]
if not value: continue
if value[0] == u" ": value = value[1:]
if attr == u"T": titles .append(value)
elif attr == u"C": song.authors = value
elif attr == u"B": comments.append(u"Source: " + value)
elif attr == u"S": comments.append(u"Source: " + value)
elif attr == u"A": comments.append(u"Region: " + value)
elif attr == u"O": comments.append(u"Origin: " + value)
elif attr == u"H": comments.append(u"History: " + value)
elif attr == u"N": comments.append(value)
elif attr == u"I": comments.append(value)
elif attr == u"L":
if rhythm2 == 8:
if value == u"1" : default_duration = 384
elif value == u"1/2" : default_duration = 192
elif value == u"1/4" : default_duration = 96
elif value == u"1/8" : default_duration = 48
elif value == u"1/16": default_duration = 24
elif value == u"1/32": default_duration = 12
elif value == u"1/64": default_duration = 6
else:
if value == u"1" : default_duration = 384
elif value == u"1/2" : default_duration = 192
elif value == u"1/4" : default_duration = 96
elif value == u"1/8" : default_duration = 48
elif value == u"1/16": default_duration = 24
elif value == u"1/32": default_duration = 12
elif value == u"1/64": default_duration = 6
#default_duration = abc_2_duration(value)
elif attr == u"R":
comments.append(value)
if (u"jig" in value) or (u"gigue" in value) or (u"syncope" in value) or (u"ternary" in value) or (u"ternaire" in value):
syncope = 1
else:
syncope = 0
elif attr == u"M":
if value == u"C" : rhythm1 = rhythm2 = 4; default_duration = 48
elif value == u"C|": rhythm1 = rhythm2 = 2; default_duration = 48
else:
rhythm1, rhythm2 = [int(i) for i in value.split(u"/")]
if float(rhythm1) / float(rhythm2) < 0.75: default_duration = 24
else: default_duration = 48
elif attr == u"Q":
if "=" in value:
duration, value = value.split("=")
tempo = int(int(value) / abc_2_duration(duration) * 96.0)
else:
tempo = int(value)
elif attr == u"P": pass # XXX
elif attr == u"K":
if (len(value) > 1) and (value[1] in u"b#"): partition.tonality = value[:2]
else: partition.tonality = value[0]
else:
if in_header:
in_header = 0
while line:
print(repr(line))
if line.startswith(u" "):
line = line[1:]
elif line.startswith(u","):
note.value -= 12
line = line[1:]
elif line.startswith(u"'"):
note.value += 12
line = line[1:]
elif line.startswith(u"^"):
note_value_offset = 1
line = line[1:]
elif line.startswith(u"_"):
note_value_offset = -1
line = line[1:]
elif line[0] in "abcdefgABCDEFGz":
if not song.mesures: # first mesure bar is usually missing in ABC
mesure = model.Mesure(song, 0, tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
start_repeat = mesure
time += current_duration
duration = int(default_duration * duration_multiplicator)
if in_tripplet:
in_tripplet -= 1
duration = int(duration / 1.5)
if line[0] == u"z": # Pause
note = None
else:
value = abc_2_value(line[0], partition.tonality)
value += note_value_offset
note = model.Note(partition, time, duration, value)
if ornementation: note.volume = 255
if in_link: note.set_link_fx("link")
partition.add_note(note)
current_duration = duration
note_value_offset = 0
duration_multiplicator = 1
ornementation = 0
line = line[1:]
elif line.startswith(u"~"):
ornementation = 1
line = line[1:]
elif line.startswith(u"{"): # XXX no grace note in Songwrite 3 yet!
ornementation = 1
line = line[line.find(u"}") + 1:]
elif line.startswith(u'"'): # XXX no chord in Songwrite 3 yet!
line = line[1:]
line = line[line.find(u'"') + 1:]
elif line.startswith(u"u") or line.startswith(u"v"):
pass # XXX
line = line[1:]
elif line.startswith(u"!"):
pass # XXX
line = line[1:]
elif line.startswith(u"."):
ornementation = 1
line = line[1:]
elif line.startswith(u"-"):
note.set__link_fx("link")
line = line[1:]
elif line.startswith(u">"):
note.duration = current_duration = int(note.duration * 1.5)
duration_multiplicator = 0.5
line = line[1:]
elif line.startswith(u"<"):
note.duration = current_duration = note.duration // 2
duration_multiplicator = 1.5
line = line[1:]
elif line.startswith(u"(3"):
in_tripplet = 3
line = line[2:]
elif line.startswith(u"("):
in_link = 1
line = line[1:]
elif line.startswith(u")"):
in_link = 0
if note: note.set_link_fx(u"")
line = line[1:]
elif line.startswith(u"|[1") or line.startswith(u"|1"):
mesure_ended()
repeated_part = song.mesures.index(start_repeat), len(song.mesures) - 1
song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
mesure = model.Mesure(song, last_mesure_end_time(), tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
time = mesure.time
current_duration = 0
start_repeat = mesure
if line.startswith(u"|[1"): line = line[3:]
else: line = line[2:]
elif line.startswith(u":|[2") or line.startswith(u":|2"):
mesure_ended()
song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
song.playlist.append(model.PlaylistItem(song.playlist, *repeated_part))
mesure = model.Mesure(song, last_mesure_end_time(), tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
time = mesure.time
current_duration = 0
start_repeat = mesure
if line.startswith(u":|[2"): line = line[4:]
else: line = line[3:]
elif line.startswith(u"|:"):
mesure_ended()
if start_repeat: song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
mesure = model.Mesure(song, last_mesure_end_time(), tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
time = mesure.time
current_duration = 0
start_repeat = mesure
line = line[2:]
elif line.startswith(u":|"):
mesure_ended()
song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
mesure = model.Mesure(song, last_mesure_end_time(), tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
time = mesure.time
current_duration = 0
line = line[2:]
start_repeat = mesure
elif line.startswith(u"::"):
mesure_ended()
song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
song.playlist.append(model.PlaylistItem(song.playlist, song.mesures.index(start_repeat), len(song.mesures) - 1))
mesure = model.Mesure(song, last_mesure_end_time(), tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
time = mesure.time
current_duration = 0
start_repeat = mesure
line = line[2:]
elif line.startswith(u"|") or line.startswith(u"||") or line.startswith(u"[|") or line.startswith(u"|]"):
mesure_ended()
mesure = model.Mesure(song, last_mesure_end_time(), tempo, rhythm1, rhythm2, syncope)
song.add_mesure(mesure)
time = mesure.time
current_duration = 0
if mesure is song.mesures[0]: start_repeat = mesure
if line.startswith(u"||") or line.startswith(u"[|") or line.startswith(u"|]"): line = line[2:]
else: line = line[1:]
else:
for abc_duration, duration in _abc_2_duration.items():
if line.startswith(abc_duration):
duration = duration * default_duration // 96
current_duration = duration
if note: note.duration = duration
line = line[len(abc_duration):]
break
song.title = u" ".join(titles)
song.comments = u" ".join(comments)
if (partition.notes[-1].time <= song.mesures[-1].time):
del song.mesures[-1]
if song.playlist.playlist_items:
try:
start_repeat_index = song.mesures.index(start_repeat)
song.playlist.append(model.PlaylistItem(song.playlist, start_repeat_index, len(song.mesures) - 1))
except ValueError:
pass
song.playlist.analyse()
# #Fix first bar, since it is often incomplete
# first_mesure_end_time = song.mesures[0].end_time()
# first_mesure_notes = [note for note in partition.notes if note.time < first_mesure_end_time]
# first_mesure_note_end_time = max([note.end_time() for note in first_mesure_notes])
# if first_mesure_note_end_time < first_mesure_end_time:
# for note in first_mesure_notes:
# note.time += first_mesure_end_time - first_mesure_note_end_time
return song
class TimingError(Exception): pass
class Timer:
def __init__(self, song):
self.song = song
self.mesures = song.mesures
self.time = 0
self.mesure_id = 0
def split(self, duration):
time = self.time
mesure_id = self.mesure_id
durations = []
while 1:
mesure_endtime = self.mesures[mesure_id].endtime()
if time + duration < mesure_endtime:
dur = duration
durations.append(dur)
time += duration
break
dur = mesure_endtime - time
durations.append(dur)
time = mesure_endtime
mesure_id += 1
duration -= dur
if duration == 0: break
return durations
def advance(self, duration, add_rest = 0):
print("avance de", duration, add_rest)
durations = []
while 1:
mesure_endtime = self.mesures[self.mesure_id].endtime()
if self.time + duration < mesure_endtime:
dur = duration
if add_rest: self.add_rest(dur)
durations.append(dur)
self.time += duration
break
dur = mesure_endtime - self.time
if add_rest: self.add_rest(dur)
durations.append(dur)
self.time = mesure_endtime
self.mesure_id += 1
duration -= dur
self.change_mesure(self.mesures[self.mesure_id - 1], self.mesures[self.mesure_id])
if duration == 0: break
return durations
def goto(self, time):
if time < self.time: raise TimingError
self.advance(time - self.time, 1)
class AbcTimer(Timer):
def __init__(self, song, abc):
Timer.__init__(self, song)
self.abc = abc
def add_rest(self, duration):
dur = 384
while duration:
nb = duration / dur
duration %= dur
for i in range(nb): self.abc.write("z" + abcduration(dur))
dur /= 2
def start(self):
for s in self.song.playlist.symbols.get(self.mesures[0], ()):
if s.startswith(r"\repeat volta"):
self.abc.write("|:")
def end(self):
self.abc.write("|]")
def change_mesure(self, left, right):
print("change mesure")
#print(left, self.song.playlist.symbols.get(left, None), right, self.song.playlist.symbols.get(right, None))
for s in self.song.playlist.symbols.get(left, ()):
if s == r"} % end repeat": self.abc.write(":")
elif s == r"} % end repeat with alternatives": self.abc.write(":")
elif s == r"} % end alternative": self.abc.write(":")
elif s == r"} % end last alternative": self.abc.write(":")
self.abc.write("|")
for s in self.song.playlist.symbols.get(right, ()):
if s.startswith(r"\repeat volta"): self.abc.write(":")
elif s.startswith(r"{ % start alternative"): self.abc.write("[" + s.split()[-1])
def abctab(s):
abc = cStringIO.StringIO()
print("%%!abctab2ps -paper %s" % {"a3paper" : "a3", "a4paper" : "a4", "a5paper" : "a5", "letterpaper" : "letter", }[globdef.config.PAGE_FORMAT], file = abc)
print("X:1", file = abc)
print("L:1/4", file = abc)
if s.title : print("T:%s" % s.title, file = abc)
if s.authors : print("C:%s" % s.authors, file = abc)
if s.comments: print("N:%s" % s.comments.replace("\n", "N:\n"), file = abc)
if s.filename: print("F:%s" % s.filename, file = abc)
for partition in s.partitions:
if hasattr(partition, "tonality"):
print("K:%s" % partition.tonality, file = abc)
break
else:
print("K:C", file = abc)
nb = 0
for partition in s.partitions:
if partition.view.__class__.__name__ == "Tablature":
clef = " clef=guitartab"
tab = 6
elif partition.view.__class__.__name__ == "Staff":
clef = ""
tab = 0
else: continue
lyric = None # XXX
tonality = getattr(partition, "tonality", "C")
print("%", file = abc)
print("V:%s name=%s%s" % (nb, partition.header, clef), file = abc)
print("K:%s" % tonality, file = abc)
nb += 1
timer = AbcTimer(s, abc)
timer.start()
chords = chordify(partition.notes)
for chord in chords:
timer.goto(chord.time)
if tab:
for duration in timer.split(chord.duration):
frets = [","] * tab
for note in chord.notes:
stringid = partition.view.stringid(note)
frets[stringid] = _tabnotes[note.value - partition.view.strings[stringid].basenote]
while frets and (frets[-1] == ","): frets.pop()
abc.write("[%s%s]" % ("".join(frets), abcduration(duration)))
timer.advance(duration)
else:
for duration in timer.advance(chord.duration):
for note in chord.notes:
abc.write(
"[" * ((len(chord.notes) > 1) and (note is chord.notes[0])) +
_notes[tonality][note.value % 12] +
"," * (4 - note.value / 12) +
"'" * (note.value / 12 - 4) +
abcduration(duration, note) +
"]" * ((len(chord.notes) > 1) and (note is chord.notes[-1]))
)
timer.end()
#open("/home/jiba/tmp/test.abc", "w").write(abc.getvalue())
return abc.getvalue()
class Chord:
def __init__(self, time):
self.time = time
self.notes = []
self.duration = 1000000
def add(self, note):
self.notes.append(note)
self.duration = min(note.duration, self.duration)
def __repr__(self):
return "" % (self.time, self.duration, self.notes)
def chordify(notes):
chords = []
previous = None
notes = notes[:]
while notes:
chord = Chord(notes[0].time)
chords.append(chord)
while notes:
if notes[0].time == chord.time: chord.add(notes.pop(0))
else: break
if previous:
if previous.time + previous.duration > chord.time: # Overlaps !!!
previous.duration = chord.time - previous.time
previous = chord
return chords
Songwrite3-0.1/plugins/abc/__init__.py 0000664 0023421 0023421 00000002047 12727241645 017606 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import songwrite3.plugins as plugins
class ABCImportPlugin(plugins.ImportPlugin):
def __init__(self):
plugins.ImportPlugin.__init__(self, "ABC", [".abc", ".txt"])
def import_from_string(self, data):
import songwrite3.plugins.abc.abc
return songwrite3.plugins.abc.abc.parse(data)
ABCImportPlugin()
Songwrite3-0.1/plugins/additional_instruments/ 0000755 0023421 0023421 00000000000 13155312423 021513 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/additional_instruments/drawer.py 0000664 0023421 0023421 00000002345 12724357177 023377 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2012 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
import songwrite3.canvas as canvas
class LuteDrawer(canvas.TablatureDrawer):
def __init__(self, canvas_, partition, compact = False):
canvas.TablatureDrawer.__init__(self, canvas_, partition, compact)
def on_key_press(self, event):
keyval = event.key()
if qtcore.Qt.Key_A <= keyval <= qtcore.Qt.Key_N: # a-n
self.canvas.on_number_typed(str(keyval - qtcore.Qt.Key_A))
return 1
Songwrite3-0.1/plugins/additional_instruments/__init__.py 0000664 0023421 0023421 00000010165 12724357133 023641 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007-2011 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import songwrite3.model as model
import songwrite3.plugins as plugins
plugins.Plugin("additional_instruments")
class Banjo5GTablatureString(model.TablatureString):
def value_2_text(self, note):
if note.value == self.base_note: return "0"
return unicode(note.value - self.base_note - note.partition.capo + 5)
def text_2_value(self, note, text):
text = int(text)
if text <= 5: return self.base_note + note.partition.capo
return text + self.base_note + note.partition.capo - 5
model.Banjo5GTablatureString = Banjo5GTablatureString
class Banjo5GView(model.TablatureView):
default_instrument = 105
def __init__(self, partition, name = ""):
super(Banjo5GView, self).__init__(partition, name or _("Banjo 5G"), self.new_strings())
@classmethod
def new_strings(Class):
return [model.TablatureString(62, -1), model.TablatureString(59, -1), model.TablatureString(55, -1), model.TablatureString(50, -1), Banjo5GTablatureString(67, -1)]
model.VIEWS["tab"].append(Banjo5GView)
class UkuleleView(model.TablatureView):
default_instrument = 27
def __init__(self, partition, name = ""):
super(UkuleleView, self).__init__(partition, name or _("Ukulele"), self.new_strings())
@classmethod
def new_strings(Class):
return [model.TablatureString(69, -1), model.TablatureString(64, -1), model.TablatureString(60, -1), model.TablatureString(55, -1)]
model.VIEWS["tab"].append(UkuleleView)
class MandolinView(model.TablatureView):
default_instrument = 105
def __init__(self, partition, name = ""):
super(MandolinView, self).__init__(partition, name or _("Mandolin"), self.new_strings())
@classmethod
def new_strings(Class):
return [model.TablatureString(76, -1), model.TablatureString(69, -1), model.TablatureString(62, -1), model.TablatureString(55, -1)]
model.VIEWS["tab"].append(MandolinView)
class KoyabuView(model.TablatureView):
default_instrument = 27
def __init__(self, partition, name = ""):
super(KoyabuView, self).__init__(partition, name or _("Koyabu board"), self.new_strings())
@classmethod
def new_strings(Class):
return [
model.TablatureString(24, -1),
model.TablatureString(31, -1),
model.TablatureString(38, -1),
model.TablatureString(45, -1),
model.TablatureString(52, -1),
model.TablatureString(59, -1),
model.TablatureString(64, -1),
model.TablatureString(57, -1),
model.TablatureString(52, -1),
model.TablatureString(47, -1),
model.TablatureString(42, -1),
model.TablatureString(37, -1),
]
model.VIEWS["tab"].append(KoyabuView)
class LuteView(model.TablatureView):
default_instrument = 27
def __init__(self, partition, name = ""):
super(LuteView, self).__init__(partition, name or _("Lute"), self.new_strings())
@classmethod
def new_strings(Class):
return [model.LuteTablatureString(69, -1),
model.LuteTablatureString(64, -1),
model.LuteTablatureString(60, -1),
model.LuteTablatureString(55, -1)]
def get_drawer(self, canvas, compact = False):
from songwrite3.plugins.additional_instruments.drawer import LuteDrawer
return LuteDrawer(canvas, self.partition, compact)
model.VIEWS["tab"].append(LuteView)
class LuteTablatureString(model.TablatureString):
def value_2_text(self, note):
if note.value == self.base_note: return "a"
return chr(note.value - self.base_note - note.partition.capo + 97)
model.LuteTablatureString = LuteTablatureString
Songwrite3-0.1/plugins/midi/ 0000755 0023421 0023421 00000000000 13155312424 015653 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/midi/importer.py 0000664 0023421 0023421 00000017054 12727253317 020110 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2001-2015 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, struct
import songwrite3.model as model
import songwrite3.midi as midi
def parse(file, rich_midi_tablature = 0):
song = model.Song()
partitions = {}
def channel2partition(channel):
return partitions.get(channel) or new_partition(channel)
def new_partition(channel):
partition = model.Partition(song)
song.partitions.append(partition)
if channel == 9: # Drums
partition.instrument = 128
partitions[channel] = partition
return partition
name, length = struct.unpack(">4si", file.read(8))
if name != b"MThd": raise ValueError("Not a midi file !", file)
format, nb_tracks, tempo = struct.unpack(">hhh", file.read(6))
for i in range(nb_tracks):
name, length = struct.unpack(">4si", file.read(8))
if name != b"MTrk": raise ValueError("Not a track !", file)
partitions = {}
time = 0
opened_notes = {}
string_id = None
fx = ""
end_at = file.tell() + length
while file.tell() < end_at:
time = time + midi.readVarLength(file)
event = struct.unpack(">B", file.read(1))[0]
if 0x80 <= event < 0x90: # Note off
channel = event - 0x80
value = struct.unpack(">B", file.read(1))[0]
try:
note = opened_notes[channel, value]
note.duration = time - note.time
del opened_notes[channel, value]
except:
print("Warning ! Note off without note on at time %s !" % time, file = sys.stderr)
file.read(1)
elif 0x90 <= event < 0xA0: # Note on
channel = event - 0x90
value, volume = struct.unpack(">BB", file.read(2))
if volume == 0: # A Note on with wolume == 0 is a Note off ???
try:
note = opened_notes[channel, value]
note.duration = time - note.time
del opened_notes[channel, value]
except:
print("Warning ! Note off without note on at time %s !" % time, file = sys.stderr)
else:
note = opened_notes[channel, value] = model.Note(channel2partition(channel), time, 256, value, volume) # Duration is unknown
if string_id is not None:
note.string_id = string_id
string_id = None
if fx:
if fx in ("link", "slide"): note.link_fx = fx
else: note.fx = fx
fx = ""
channel2partition(channel).add_note(note)
elif 0xA0 <= event < 0xB0:
print("Warning ! aftertouch not supported !", file = sys.stderr)
file.read(2)
elif 0xB0 <= event < 0xC0:
partition = channel2partition(event - 0xB0)
event = struct.unpack(">B", file.read(1))[0]
if event == 0x5B: partition.reverb = struct.unpack(">B", file.read(1))[0]
elif event == 0x5D: partition.chorus = struct.unpack(">B", file.read(1))[0]
elif event == 0x07: partition.volume = struct.unpack(">B", file.read(1))[0] * 2
else:
print("Warning ! unknown midi controller : %s, value : %s" % (hex(event), struct.unpack(">B", file.read(1))[0]), file = sys.stderr)
elif 0xC0 <= event < 0xD0:
partition = channel2partition(event - 0xC0)
if partition.instrument != 128: # Else it is drums ; an instrument event has no sens for the drums channel but it occurs in some mids...
partition.instrument = struct.unpack(">B", file.read(1))[0]
elif 0xD0 <= event < 0xE0:
print("Warning ! aftertouch not supported !", file = sys.stderr)
file.read(1)
elif 0xE0 <= event < 0xF0:
print("Warning ! pitchwheel not supported ! Use rich midi if you want to import hammer/bend/....", file = sys.stderr)
file.read(1)
file.read(1)
elif event == 0xF0: # System exclusive
while struct.unpack(">B", file.read(1))[0] != 0xF7: pass
elif event == 0xFF:
event = struct.unpack(">B", file.read(1))[0]
length = struct.unpack(">B", file.read(1))[0]
content = file.read(length)
if event == 0x01: # Comment ?
if song.comments: song.comments = song.comments + "\n" + content
else: song.comments = content
elif event == 0x02: # Copyright ?
if song.copyright: song.copyright = song.copyright + content
else: song.copyright = content
elif event == 0x10: # Rich tab midi event
channel_id = struct.unpack(">B", content[0:1])[0]
partition = channel2partition(channel_id - 1)
partition.capo = struct.unpack(">B", content[1:2])[0]
strings = []
for c in content[2:]:
strings.append(model.TablatureString(struct.unpack(">B", c)[0]))
partition.view = model.TablatureView(partition, "", strings)
elif event == 0x11: # Rich tab midi event
if rich_midi_tablature:
string_id = struct.unpack(">B", content[0:1])[0]
if len(content) > 1:
spc = struct.unpack(">B", content[1:2])[0]
if spc == 0x01: fx = "link"
elif spc == 0x02: fx = "link"
elif spc == 0x03: fx = "slide"
elif spc == 0x04: fx = "slide"
elif spc == 0x06: fx = "roll"
elif spc == 0x0A: fx = "tremolo"
elif spc == 0x0E: fx = "dead"
elif spc == 0x0C:
fx = "bend"
if len(content) > 2: bend_pitch = struct.unpack(">B", content[2:3])[0] * 0.25
else: bend_pitch = 0.5
else: print("Warning ! unknown rich midi special effect :", hex(spc), file = sys.stderr)
elif event == 0x2F: # End of track
file.seek(end_at)
break
else:
print("Warning ! unknow sequence 0xFF", hex(event), " content (%s bytes) : %s" % (len(content), content), file = sys.stderr)
else:
print("Warning ! unknown midi event :", hex(event), file = sys.stderr)
continue
for partition in song.partitions:
if partition.instrument == 128: # Drums
strings = {}
for note in partition.notes:
if not note.value in strings:
strings[note.value] = model.DrumString(note.value)
for mesure in song.mesures: mesure.tempo = tempo
# Start the song at time == 0
if song.partitions:
start = min([min([sys.maxsize] + [note.time for note in partition.notes]) for partition in song.partitions])
for partition in song.partitions:
for note in partition.notes:
note.time = note.time - start
if not partition.view:
partition.view = model.GuitarView(partition)
return song
Songwrite3-0.1/plugins/midi/__init__.py 0000664 0023421 0023421 00000003750 12727252610 017777 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007-2015 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
from io import StringIO, BytesIO
import songwrite3.plugins as plugins
import songwrite3.midi as midi
class MidiExportPlugin(plugins.ExportPlugin):
def __init__(self):
plugins.ExportPlugin.__init__(self, "MIDI", [".mid", ".midi"])
def export_to_string(self, song):
return midi.song_2_midi(song)
MidiExportPlugin()
class MidiImportPlugin(plugins.ImportPlugin):
def __init__(self):
plugins.ImportPlugin.__init__(self, "MIDI", [".mid", ".midi"], binary = True)
def import_from_string(self, data):
import songwrite3.plugins.midi.importer as importer
return importer.parse(BytesIO(data))
MidiImportPlugin()
class RichMidiExportPlugin(plugins.ExportPlugin):
def __init__(self):
plugins.ExportPlugin.__init__(self, "RichMIDI", [".mid", ".midi"])
def export_to_string(self, song):
return midi.song_2_midi(song, rich_midi_tablature = 1)
RichMidiExportPlugin()
class RichMidiImportPlugin(plugins.ImportPlugin):
def __init__(self):
plugins.ImportPlugin.__init__(self, "RichMIDI", [".mid", ".midi"], binary = True)
def import_from_string(self, data):
import songwrite3.plugins.midi.importer as importer
return importer.parse(BytesIO(data), rich_midi_tablature = 1)
RichMidiImportPlugin()
Songwrite3-0.1/plugins/songwrite/ 0000755 0023421 0023421 00000000000 13155312424 016752 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/songwrite/__init__.py 0000664 0023421 0023421 00000002544 12564643306 021103 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007-2015 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
from io import StringIO
import songwrite3.plugins as plugins
class ExportPlugin(plugins.ExportPlugin):
def __init__(self):
plugins.ExportPlugin.__init__(self, "Songwrite", ["sw.xml"], 1, 1)
def export_to_string(self, song):
return song.__xml__().getvalue()
def create_ui(self, app): pass
ExportPlugin()
class ImportPlugin(plugins.ImportPlugin):
def __init__(self):
plugins.ImportPlugin.__init__(self, "Songwrite", ["sw.xml"])
def import_from_string(self, data):
import songwrite3.stemml as stemml
return stemml.parse(StringIO(data))
def create_ui(self, app): pass
ImportPlugin()
Songwrite3-0.1/plugins/fingering/ 0000755 0023421 0023421 00000000000 13155312423 016700 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/fingering/drawer.py 0000664 0023421 0023421 00000021576 12702411635 020555 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2008 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
import editobj3
import songwrite3.canvas as canvas
class FingeringDrawer(canvas.PartitionDrawer):
def __init__(self, canvas_, partition, compact, String):
canvas.PartitionDrawer.__init__(self, canvas_, partition, compact)
self.height = 300
self.strings = [String(self)]
self.string_height = self.canvas.default_line_height * 0.3
for hole in next(iter(partition.view.fingerings.values())):
if hole is None: self.string_height += self.canvas.default_line_height * 0.3
else: self.string_height += self.canvas.default_line_height * 0.6
self.stem_extra_height = self.canvas.default_line_height
self.lh2 = self.canvas.default_line_height / 2
self.lh3 = self.canvas.default_line_height / 3
self.lh4 = self.canvas.default_line_height / 4.5
self.lh7 = self.canvas.default_line_height / 7
self.lh_6 = self.canvas.default_line_height * 0.6
def partition_listener(self, partition, type, new, old):
if type is object:
if (new.get("instrument_tonality") != old.get("instrument_tonality")):
self.canvas.render_all()
def note_width(self, note): return 9.0 * self.scale
def note_string_id(self, note): return 0 # No "string" notion in fingering notation!
def y_2_string_id(self, y, force = 0):
if force:
if self.y + self.start_y > y: return -1
elif y > self.y + self.height: return 1
return 0
else:
if self.y + self.start_y <= y <= self.y + self.height: return 0
return None
def string_id_2_y(self, string_id = 0):
return self.y + self.start_y + self.stem_extra_height + self.string_height // 2
def draw_mesure(self, ctx, time, mesure, y, height):
canvas.PartitionDrawer.draw_mesure(self, ctx, time, mesure, self.y + self.start_y + self.stem_extra_height, self.string_height)
def draw_strings(self, ctx, x, y, width, height):
self.height = int(self.start_y + self.stem_extra_height + self.string_height + self.stem_extra_height_bottom + 1)
def note_text(self, note): return "_"
def note_dimensions(self, note):
x = self.canvas.time_2_x(note.time)
y = self.string_id_2_y()
return (
x + 3.0 * self.scale,
y - self.string_height // 2,
x + max(note.duration * self.canvas.zoom, self.canvas.char_h_size * 1.9),
y + self.string_height // 2,
)
def draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure = None, previous_time = -32000, next_time = 32000):
x = x + self.canvas.default_line_height // 2
canvas.PartitionDrawer.draw_stem_and_beam(self, ctx, x, note, y1, y2, mesure, previous_time, next_time)
def draw_note(self, ctx, note, string_id, y):
color = self.note_color(note)
ctx.setPen(color)
ctx.setBrush(color)
x = self.canvas.time_2_x(note.time)
y -= self.string_height // 2
if note.is_dotted():
#ctx.arc(x + self.canvas.default_line_height // 2 + 4 * self.scale, y - 3 * self.scale, 1.5, 0, 6.2831853071795862)
#ctx.fill()
ctx.drawEllipse(
x + self.canvas.default_line_height // 2 + 2.5 * self.scale,
y - 4.5 * self.scale,
1.5 * self.scale,
1.5 * self.scale,
)
fingering = self.partition.view.note_2_fingering(note)
if fingering:
holes = fingering[:-1]
breath = fingering[-1]
else:
holes = []
breath = "?"
for hole in holes:
if hole is None:
y += self.lh3
continue
elif hole == 1:
ctx.drawEllipse(x + self.lh2 - self.lh4, y + self.lh3 - self.lh4, self.lh4 * 2.0 + 0.5, self.lh4 * 2.0 + 0.5)
elif hole == 0:
ctx.setBrush(qtcore.Qt.transparent)
ctx.drawEllipse(x + self.lh2 - self.lh4, y + self.lh3 - self.lh4, self.lh4 * 2.0 + 0.5, self.lh4 * 2.0 + 0.5)
ctx.setBrush(color)
elif hole == 0.5:
ctx.setBrush(qtcore.Qt.transparent)
ctx.drawEllipse(x + self.lh2 - self.lh4, y + self.lh3 - self.lh4, self.lh4 * 2.0 + 0.5, self.lh4 * 2.0 + 0.5)
ctx.setBrush(color)
ctx.drawChord (x + self.lh2 - self.lh4, y + self.lh3 - self.lh4, self.lh4 * 2.0 + 0.5, self.lh4 * 2.0 + 0.5, 0.0, 180 * 16)
else:
x2 = x + self.lh2 - self.lh3 + self.lh7
for sub_hole in hole:
if sub_hole == 1:
ctx.drawEllipse(x2 - self.lh7, y + self.lh3 - self.lh7, self.lh7 * 2.0, self.lh7 * 2.0)
elif sub_hole == 0:
ctx.setBrush(qtcore.Qt.transparent)
ctx.drawEllipse(x2 - self.lh7, y + self.lh3 - self.lh7, self.lh7 * 2.0, self.lh7 * 2.0)
ctx.setBrush(color)
x2 += self.lh3
y += self.lh_6
if breath:
ctx.drawText(x + self.canvas.default_line_height * 0.2,
y + self.canvas.default_line_height * 0.8,
breath)
if note.fx : getattr(self, "draw_note_fx_%s" % note.fx )(ctx, note, string_id)
if note.link_fx: getattr(self, "draw_note_fx_%s" % note.link_fx)(ctx, note, string_id)
ctx.setPen(qtcore.Qt.black)
ctx.setBrush(qtcore.Qt.black)
def on_touchscreen_new_note(self, event):
y = event.y() - self.y - self.start_y - self.stem_extra_height
hole_clicked = -1
for hole in next(iter(self.partition.view.fingerings.values())):
if y < 0: break
hole_clicked += 1
if hole is None: y -= self.canvas.default_line_height * 0.3
else: y -= self.canvas.default_line_height * 0.6
new_value = self.strings[0].on_click_at(hole_clicked)
self.canvas.on_number_typed(new_value)
return 1 # OK to change note by dragging up or down
class SingleString(object):
def __init__(self, drawer, notation_pos = -1):
self.drawer = drawer
self.notation_pos = notation_pos
def get_base_note(self): return self.drawer.partition.view.get_bell_note()
base_note = property(get_base_note)
def text_2_value(self, note, text): return self.base_note
def value_2_text(self, note): return "o"
def __str__(self): return "Single string"
def width(self): return -1
def on_click_at(self, hole): return "0"
class TinWhistleSingleString(SingleString):
def text_2_value(self, note, text):
if text == "0": return self.base_note + 11
elif text == "9": return self.base_note + 10
elif text == "1": return self.base_note + 9
elif text == "2": return self.base_note + 7
elif text == "3": return self.base_note + 5
elif text == "4": return self.base_note + 4
elif text == "5": return self.base_note + 2
elif text == "6": return self.base_note
elif text == "00": return self.base_note + 23
elif text == "11": return self.base_note + 21
elif text == "22": return self.base_note + 19
elif text == "33": return self.base_note + 17
elif text == "44": return self.base_note + 16
elif text == "55": return self.base_note + 14
elif text == "66": return self.base_note + 12
if len(text) > 2: return self.text_2_value(note, text[-2:])
if len(text) > 1: return self.text_2_value(note, text[-1])
return self.base_note
def on_click_at(self, note, hole):
current_fingering = self.drawer.partition.view.note_2_fingering(note)
g8 = 0
if current_fingering and (current_fingering[-1] == "+"): breath = 12
else: breath = 0
if hole < 0: return self.base_note + g8 + breath + 11
elif hole == 0: return self.base_note + g8 + breath + 9
elif hole == 1: return self.base_note + g8 + breath + 7
elif hole == 2: return self.base_note + g8 + breath + 5
# hole == 3 is the "None" separator
elif hole == 4: return self.base_note + g8 + breath + 4
elif hole == 5: return self.base_note + g8 + breath + 2
elif hole == 6: return self.base_note + g8 + breath
elif hole == 7: # Toggle breath
if breath: return note.value - 12
else: return note.value + 12
def on_click_at(self, hole):
if hole < 0: return "0"
elif hole == 0: return "1"
elif hole == 1: return "2"
elif hole == 2: return "3"
# hole == 3 is the "None" separator
elif hole == 4: return "4"
elif hole == 5: return "5"
elif hole == 6: return "6"
Songwrite3-0.1/plugins/fingering/__init__.py 0000664 0023421 0023421 00000016065 12702410000 021006 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2008 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import editobj3, editobj3.introsp as introsp, editobj3.field as field
import songwrite3.model as model
import songwrite3.plugins as plugins
class FingeringView(model.View):
view_class = "fingering"
default_instrument = 72
default_icon_filename = "flute.png"
def __init__(self, partition, name = ""):
model.View.__init__(self, partition, name)
if not hasattr(partition, "print_with_staff_too"): partition.print_with_staff_too = 0
def get_drawer(self, canvas, compact = False):
from songwrite3.plugins.fingering.drawer import FingeringDrawer, SingleString
return FingeringDrawer(canvas, self.partition, compact, SingleString)
def get_type(self):
return self.__class__
def note_2_fingering(self, note):
v = abs(note.value)
if getattr(self.partition, "g8", 0): v += 12
return self.fingerings.get(v)
class TinWhistleView(FingeringView):
default_instrument = 72
default_icon_filename = "flute_irlandaise.png"
fingerings = {
#note : (hole1, hole2, hole3, hole4, hole5, hole6, breath),
62 : (1 , 1 , 1 , None, 1 , 1 , 1 , ""),
63 : (1 , 1 , 1 , None, 1 , 1 , 0.5, ""),
64 : (1 , 1 , 1 , None, 1 , 1 , 0 , ""),
65 : (1 , 1 , 1 , None, 1 , 0.5, 0 , ""),
66 : (1 , 1 , 1 , None, 1 , 0 , 0 , ""),
67 : (1 , 1 , 1 , None, 0 , 0 , 0 , ""),
68 : (1 , 1 , 0.5, None, 0 , 0 , 0 , ""),
69 : (1 , 1 , 0 , None, 0 , 0 , 0 , ""),
70 : (1 , 0.5, 0 , None, 0 , 0 , 0 , ""),
71 : (1 , 0 , 0 , None, 0 , 0 , 0 , ""),
72 : (0 , 1 , 1 , None, 1 , 0 , 1 , ""),
73 : (0 , 0 , 0 , None, 0 , 0 , 0 , ""),
74 : (0 , 1 , 1 , None, 1 , 1 , 1 , "+"),
75 : (1 , 1 , 1 , None, 1 , 1 , 0.5, "+"),
76 : (1 , 1 , 1 , None, 1 , 1 , 0 , "+"),
77 : (1 , 1 , 1 , None, 1 , 0.5, 0 , "+"),
78 : (1 , 1 , 1 , None, 1 , 0 , 0 , "+"),
79 : (1 , 1 , 1 , None, 0 , 0 , 0 , "+"),
80 : (1 , 1 , 0.5, None, 0 , 0 , 0 , "+"),
81 : (1 , 1 , 0 , None, 0 , 0 , 0 , "+"),
82 : (1 , 0.5, 0 , None, 0 , 0 , 0 , "+"),
83 : (1 , 0 , 0 , None, 0 , 0 , 0 , "+"),
85 : (0 , 0 , 0 , None, 0 , 0 , 0 , "+"),
}
def __init__(self, partition, name = ""):
if not hasattr(partition, "instrument_tonality"):
partition.instrument_tonality = "D"
if not partition.notes: partition.tonality = "D"
FingeringView.__init__(self, partition, name or _("Tin whistle"))
def get_icon_filename(self): return "flute_irlandaise.png"
def note_2_fingering(self, note):
v = abs(note.value)
if getattr(self.partition, "g8", 0): v += 12
return self.fingerings.get(v - model.OFFSETS[self.partition.instrument_tonality] + 2)
def get_bell_note(self):
bell_note = 62 + model.OFFSETS[self.partition.instrument_tonality] - 2
if self.partition.g8: bell_note -= 12
return bell_note
def get_drawer(self, canvas, compact = False):
from songwrite3.plugins.fingering.drawer import FingeringDrawer, TinWhistleSingleString
return FingeringDrawer(canvas, self.partition, compact, TinWhistleSingleString)
def __xml__(self, xml = None, context = None):
xml.write(u'''\t\t\n''')
class RecorderView(FingeringView):
default_instrument = 73
fingerings = {
#note : (hole1, hole2, hole3, hole4, hole5, hole6, breath),
60 : (1 , None, 1 , 1 , 1 , None, 1 , 1 , (1, 1), (1, 1), ""),
61 : (1 , None, 1 , 1 , 1 , None, 1 , 1 , (1, 1), (1, 0), ""),
62 : (1 , None, 1 , 1 , 1 , None, 1 , 1 , (1, 1), (0, 0), ""),
63 : (1 , None, 1 , 1 , 1 , None, 1 , 1 , (1, 0), (0, 0), ""),
64 : (1 , None, 1 , 1 , 1 , None, 1 , 1 , (0, 0), (0, 0), ""),
65 : (1 , None, 1 , 1 , 1 , None, 1 , 0 , (1, 1), (1, 1), ""),
66 : (1 , None, 1 , 1 , 1 , None, 0 , 1 , (1, 1), (0, 0), ""),
67 : (1 , None, 1 , 1 , 1 , None, 0 , 0 , (0, 0), (0, 0), ""),
68 : (1 , None, 1 , 1 , 0 , None, 1 , 1 , (0, 0), (0, 0), ""),
69 : (1 , None, 1 , 1 , 0 , None, 0 , 0 , (0, 0), (0, 0), ""),
70 : (1 , None, 1 , 0 , 1 , None, 1 , 0 , (0, 0), (0, 0), ""),
71 : (1 , None, 1 , 0 , 0 , None, 0 , 0 , (0, 0), (0, 0), ""),
72 : (1 , None, 0 , 1 , 0 , None, 0 , 0 , (0, 0), (0, 0), ""),
73 : (0 , None, 1 , 1 , 0 , None, 0 , 0 , (0, 0), (0, 0), ""),
74 : (0 , None, 0 , 1 , 0 , None, 0 , 0 , (0, 0), (0, 0), ""),
75 : (0 , None, 1 , 1 , 1 , None, 1 , 1 , (1, 1), (1, 1), ""),
76 : (0.5, None, 1 , 1 , 1 , None, 1 , 1 , (0, 0), (0, 0), ""),
77 : (0.5, None, 1 , 1 , 1 , None, 1 , 0 , (1, 1), (0, 0), ""),
78 : (0.5, None, 1 , 1 , 1 , None, 0 , 1 , (0, 0), (0, 0), ""),
79 : (0.5, None, 1 , 1 , 1 , None, 0 , 0 , (0, 0), (0, 0), ""),
80 : (0.5, None, 1 , 1 , 0 , None, 1 , 0 , (0, 0), (0, 0), ""),
81 : (0.5, None, 1 , 1 , 0 , None, 0 , 0 , (0, 0), (0, 0), ""),
82 : (0.5, None, 1 , 1 , 0 , None, 1 , 1 , (1, 1), (0, 0), ""),
83 : (0.5, None, 1 , 1 , 0 , None, 1 , 1 , (0, 0), (0, 0), ""),
84 : (0.5, None, 1 , 0 , 0 , None, 1 , 1 , (0, 0), (0, 0), ""),
85 : (0.5, None, 1 , 0 , 1 , None, 1 , 0 , (0, 0), (0, 0), ""),
86 : (0.5, None, 1 , 0 , 1 , None, 1 , 0 , (1, 1), (0, 0), ""),
87 : (0.5, None, 0 , 1 , 1 , None, 0 , 1 , (1, 1), (0, 0), ""),
}
def __init__(self, partition, name = ""):
FingeringView.__init__(self, partition, name or _("Recorder"))
def get_icon_filename(self): return "flute.png"
def get_bell_note(self):
bell_note = 60
if self.partition.g8: bell_note -= 12
return bell_note
def __xml__(self, xml = None, context = None):
xml.write(u'''\t\t''')
model.VIEW_CATEGORIES.append("fingering")
plugins.ViewPlugin(TinWhistleView, None, "tin_whistle", "fingering")
plugins.ViewPlugin(RecorderView , None, "recorder" , "fingering")
#descr = introsp.description(model.Partition)
#descr.set_field_for_attr("instrument_tonality" , field.EnumField(dict([(_("tonality_%s" % tonality), tonality) for tonality in model.TONALITIES.keys()])))
Songwrite3-0.1/plugins/texttab/ 0000755 0023421 0023421 00000000000 13155312424 016404 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/texttab/texttab.py 0000664 0023421 0023421 00000025107 12727254132 020445 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2001-2007 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, array, re
import string as string_module # not a guitar string !
from io import StringIO
import songwrite3.model as model
def parse(texttab):
ASCII_STRING = "-="
ASCII_BAR = "|"
ASCII_STRESSED = "'"
ASCII_HAMMER = "hpl"
ASCII_SLIDE = "/\\s"
ASCII_BEND = "b"
ASCII_DEAD_NOTE = "xd"
ASCII_TREMOLO = "~vt"
ASCII_TAB = ASCII_STRING + ASCII_BAR + string_module.digits + ASCII_STRESSED + ASCII_HAMMER + ASCII_SLIDE + ASCII_BEND + ASCII_DEAD_NOTE + ASCII_TREMOLO
TITRE_AUTHOR = re.compile(r".*\(.*\)")
COPYRIGHT = re.compile(r"(C|c)opy(right|left)\s*[0-9]{2,4}")
TEMPO = re.compile(r"((?<=(T|t)empo(:|=)\s)|(?<=(T|t)empo(:|=))|(?<=(T|t)empo\s(:|=)\s)|(?<=(T|t)empo\s))([0-9]+)")
song = model.Song()
lines = texttab.split("\n")
tempo = TEMPO.findall(texttab)
if tempo: tempo = tempo[0][-1]
else: tempo = 60
i = 0
header = ""
song_comments_ended = 0
if (not lines[i]) or (not lines[i][0] in ASCII_TAB): # First line is guessed to be the title.
if TITRE_AUTHOR.match(lines[i]):
start = lines[i].find("(")
song.title = lines[i][: start]
song.authors = lines[i][start + 1 : lines[i].find(")")]
else: song.title = lines[i]
i = i + 1
while 1:
if not lines[i]: song_comments_ended = 1
elif not lines[i][0] in ASCII_TAB:
if COPYRIGHT.search(lines[i]): song.copyright = lines[i]
else:
if song_comments_ended:
if header: header = header + "\n" + lines[i]
else: header = lines[i]
else:
if song.comments: song.comments = song.comments + "\n" + lines[i]
else: song.comments = lines[i]
else: break
i = i + 1
# Compute rythm
if lines[i][0] in ASCII_BAR: mesure_len = lines[i][1:].find("|")
else: mesure_len = lines[i].find("|")
if mesure_len % 4 == 0: unit = float(mesure_len / 4) # Guess it is 4/4
elif mesure_len % 3 == 0: unit = float(mesure_len / 3) # Guess it is 3/4
pos = []
last_bar_pos = 0
partition = model.Partition(song)
partition.header = header
# adrian@sixfingeredman.net: hack to guess tuning
if "DADGAD" in header: partition.view = model.GuitarDADGADView(partition)
else: partition.view = model.GuitarView (partition)
current_note = None
next_note = model.Note(partition, 0, 96, 0)
song.partitions.append(partition)
song.mesures *= 0
while (i < len(lines)):
string_id = 0
while (i < len(lines)) and lines[i] and (lines[i][0] in ASCII_TAB):
j = 0
if string_id >= len(pos) - 1: pos.append(0)
while j < len(lines[i]):
char = lines[i][j]
if char in ASCII_BAR:
if (string_id == 0) and (partition is song.partitions[0]):
if j > 1:
song.add_mesure(model.Mesure(song,
int(round(last_bar_pos / unit * 96.0)),
rythm1 = int(round((pos[string_id] - last_bar_pos) / unit)),
tempo = tempo))
last_bar_pos = pos[string_id]
j = j + 1
continue
elif char in string_module.digits:
if (j + 1 < len(lines[i])) and (lines[i][j + 1] in string_module.digits):
fret = int(char + lines[i][j + 1])
pos[string_id] = pos[string_id] + 1
j = j + 1
else: fret = int(char)
next_note.value = partition.view.strings[string_id].base_note + fret
next_note.time = int(round((pos[string_id] - 1)/ unit * 96.0))
next_note.string_id = string_id
partition.notes.append(next_note)
current_note = next_note
next_note = model.Note(partition, 0, 96, 0)
elif char in ASCII_STRESSED : next_note.volume = 255
elif char in ASCII_TREMOLO : next_note.fx = "tremolo"
elif char in ASCII_DEAD_NOTE: next_note.fx = "dead"
elif char in ASCII_BEND : next_note.fx = "bend"
elif char in ASCII_HAMMER:
current_note.link_fx = "link"
current_note.link_to(next_note)
elif char in ASCII_SLIDE:
current_note.link_fx = "slide"
current_note.link_to(next_note)
elif char in ASCII_STRING: pass
else: print("* parse_texttab * Unknown character %s at line %s, position %s ! Skipping." % (char, i, j), file = sys.stderr)
pos[string_id] = pos[string_id] + 1
j = j + 1
i = i + 1
string_id = string_id + 1
while (i < len(lines)) and (not lines[i]): i = i + 1
if (i < len(lines)) and ((len(lines[i]) < 3) or (not lines[i][0] in ASCII_TAB) or (not lines[i][1] in ASCII_TAB)): break
# Compute notes' durations
partition.notes.sort()
last_notes = []
for note in partition.notes:
if last_notes and (last_notes[0].time != note.time):
for last_note in last_notes:
last_note.duration = note.time - last_note.time
last_notes = []
last_notes.append(note)
last_duration = song.mesure_at(last_notes[0].time, 1).end_time() - last_notes[0].time
for last_note in last_notes:
last_note.duration = last_duration
return song
def texttab(song, cut_tab_at = 80):
"texttab(song) -> string -- Generates an ascii tablature from a song"
min_duration = sys.maxsize
ternary = 0
partitions = []
for partition in song.partitions:
if isinstance(partition, model.Partition) and isinstance(partition.view, model.TablatureView):
partitions.append(partition)
for note in partition.notes:
dur = note.duration
if note.is_triplet(): # Triplets are a hack...
ternary = 1
if note.duration == 32: dur = 48
if note.duration == 16: dur = 24
if note.duration == 8: dur = 12
else:
rest = note.time % min_duration
if rest > 0: min_duration = rest
if dur < min_duration: min_duration = dur
min_duration = float(min_duration)
slotsize = 2
for partition in song.partitions:
if isinstance(partition, model.Partition) and isinstance(partition.view, model.TablatureView):
for note in partition.notes:
if note.is_triplet():
slotsize = 3
break
duration = min_duration / slotsize
alltabs = StringIO()
alltabs.write("%s (%s)\n" % (song.title, song.authors))
if song.copyright: alltabs.write(song.copyright + "\n")
if song.comments : alltabs.write(song.comments + "\n")
alltabs.write("\n")
for partition in song.partitions:
if isinstance(partition, model.Lyrics):
alltabs.write(partition.header + "\n")
text = partition.text.replace("\t_", "").replace("_", "").replace("-\t", "").replace("\t", " ").replace("\n", "").replace("\\\\", "\n").replace(" ", " ")
alltabs.write("\n".join(i.strip() for i in text.split("\n")))
alltabs.write("\n")
elif isinstance(partition.view, model.TablatureView):
alltabs.write(partition.header + "\n")
strings = partition.view.strings
tabs = [array.array("u", "-" * int(round(partition.end_time() / min_duration * slotsize))) for string in strings]
def string_id(note):
return getattr(note, "string_id", len(strings) - 1)
for note in partition.notes:
sid = string_id(note)
time = int(round(note.time / min_duration * slotsize))
text = strings[sid].value_2_text(note)
if note.linked_from:
if note.linked_from.link_fx == "link":
link_type = note.linked_from.link_type()
if link_type == 0: continue # Don't show linked note, since ascii tab doesn't matter note duration !
elif link_type == -1: tabs[sid][time] = "p"
elif link_type == 1: tabs[sid][time] = "h"
elif note.linked_from.link_fx == "slide":
if note.linked_from.value > note.value: tabs[sid][time] = "\\"
else: tabs[sid][time] = "/"
if note.fx == "dead" : tabs[sid][time] = "x"
elif note.fx == "tremolo": tabs[sid][time] = "~"
elif note.fx == "bend" : tabs[sid][time] = "b"
if (note.volume > 220) and (tabs[sid][time] == "-"): tabs[sid][time] = "'" # Stressed note
if len(text) == 1: tabs[sid][time + 1] = text
else:
tabs[sid][time + 1] = text[0]
tabs[sid][time + 2] = text[1]
tabs = [tab.tounicode() for tab in tabs] # Turn arrays to strings.
mesure = song.mesures[0]
i = 0
while i < len(song.mesures):
mesures = []
length = 1
while i < len(song.mesures):
length = length + mesure.duration / min_duration * slotsize + 1
if length > cut_tab_at: break
mesures.append(mesure)
i = i + 1
if i >= len(song.mesures): break
mesure = song.mesures[i]
string_base_note_length = max([len(model.note_label(string.base_note, False)) for string in strings])
string_base_note_pattern = "%%-%ss" % string_base_note_length
for sid in range(len(tabs)):
tab = tabs[sid]
# adrian@sixfingeredman.net: this screws up the importer
# alltabs.write(string_base_note_pattern % model.note_label(strings[sid].base_note, False))
for mes in mesures:
content = tab[int(round(mes.time / min_duration * slotsize)) : int(round(mes.end_time() / min_duration * slotsize))]
if not content:
i = sys.maxsize
break
alltabs.write("|" + content)
alltabs.write("|\n")
alltabs.write("\n")
return alltabs.getvalue()
Songwrite3-0.1/plugins/texttab/__init__.py 0000664 0023421 0023421 00000002774 12727241664 020544 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import songwrite3.model as model
import songwrite3.plugins as plugins
class ExportPlugin(plugins.ExportPlugin):
def __init__(self):
plugins.ExportPlugin.__init__(self, "TextTab", [".txt"], 1, 1)
def export_to_string(self, song):
import songwrite3.plugins.texttab.texttab as texttab
if isinstance(song, model.Songbook):
return "\n\n\n\n".join(texttab.texttab(song_ref.get_song()) for song_ref in song.song_refs)
else:
return texttab.texttab(song)
ExportPlugin()
class ImportPlugin(plugins.ImportPlugin):
def __init__(self):
plugins.ImportPlugin.__init__(self, "TextTab", [".txt"])
def import_from(self, filename):
import songwrite3.plugins.texttab.texttab as texttab
return texttab.parse(open(filename).read())
ImportPlugin()
Songwrite3-0.1/plugins/lyre/ 0000755 0023421 0023421 00000000000 13155312424 015704 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/plugins/lyre/drawer.py 0000664 0023421 0023421 00000020502 12702424250 017541 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2008-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
import songwrite3.model as model
import songwrite3.canvas as canvas_module
class LyreDrawer(canvas_module.TablatureDrawer):
def __init__(self, canvas, partition, compact = False):
canvas_module.TablatureDrawer.__init__(self, canvas, partition)
self.compact = compact
if compact: self.string_height = canvas.default_line_height / 1.5
else: self.string_height = 0.8 * canvas.default_line_height + 2 * self.scale
self.note_offset_x = 0.66 * self.string_height
self.note_offset_y = -10.0 * self.scale #-self.string_height // 22.0
def mouse_motion_note(self, value, delta): return value + delta
def note_listener(self, note, type, new, old):
if type is object:
if (new.get("fx", "") == "harmonic") and (old.get("fx", "") != "harmonic"):
string_value = self.strings[self.note_string_id(note)].base_note
if abs(string_value - note.value) < 6: note.value += 12
elif (new.get("fx", "") != "harmonic") and (old.get("fx", "") == "harmonic"):
string_value = self.strings[self.note_string_id(note)].base_note
if abs(string_value - note.value) >= 6: note.value -= 12
elif new["value"] != old["value"]:
string_value = self.strings[self.note_string_id(note)].base_note
diff = abs(string_value - note.value)
if (diff >= 6) and (note.fx == ""): note.set_fx("harmonic")
elif (diff < 6) and (note.fx == "harmonic"): note.set_fx("")
canvas_module.TablatureDrawer.note_listener(self, note, type, new, old)
create_cursor = canvas_module.PartitionDrawer.create_cursor
def note_text(self, note):
if note.fx == "dead":
return " "
s = self.strings[self.note_string_id(note)].value_2_text(note)
if (not note is self.canvas.cursor) and (s != "0") and (s != "12"): return s
if note.fx == "harmonic":
if note is self.canvas.cursor: return "_◆"
return "◆"
if note is self.canvas.cursor: return "_"
return "●"
def note_previous(self, note):
string_id = self.note_string_id(note)
while 1:
note = note.previous()
if not note: return None
if self.note_string_id(note) == string_id: return note
def draw_note(self, ctx, note, string_id, y):
x = self.canvas.time_2_x(note.time)
string = self.strings[string_id]
color = self.note_color(note)
ctx.save()
ctx.setPen(color)
ctx.setBrush(color)
if note.fx == "harmonic":
if note.duration_fx == "appoggiatura": # White appoggiatura should never exist !
ctx.drawPolygon(
qtcore.QPointF(x + 0.35 * self.string_height, y),
qtcore.QPointF(x + 0.6 * self.string_height, y + 0.25 * self.string_height),
qtcore.QPointF(x + 0.85 * self.string_height, y),
qtcore.QPointF(x + 0.6 * self.string_height, y - 0.25 * self.string_height),
)
else:
ctx.drawPolygon(
qtcore.QPointF(x + 0.2 * self.string_height, y),
qtcore.QPointF(x + 0.6 * self.string_height, y + 0.4 * self.string_height),
qtcore.QPointF(x + 1.0 * self.string_height, y),
qtcore.QPointF(x + 0.6 * self.string_height, y - 0.4 * self.string_height),
)
if note.base_duration() in (192, 384):
ctx.setBrush(qtcore.Qt.white)
ctx.drawPolygon(
qtcore.QPointF(x + 0.3 * self.string_height, y),
qtcore.QPointF(x + 0.6 * self.string_height, y + 0.3 * self.string_height),
qtcore.QPointF(x + 0.9 * self.string_height, y),
qtcore.QPointF(x + 0.6 * self.string_height, y - 0.3 * self.string_height),
)
elif note.fx != "dead":
if note.duration_fx == "appoggiatura": # White appoggiatura should never exist !
ctx.drawEllipse(qtcore.QRectF(
x + 0.35 * self.string_height,
y - 0.25 * self.string_height + 0.5,
0.5 * self.string_height,
0.5 * self.string_height,
))
else:
ctx.drawEllipse(qtcore.QRectF(
x + 0.2 * self.string_height,
y - 0.4 * self.string_height + 0.5,
0.8 * self.string_height,
0.8 * self.string_height,
))
if note.base_duration() in (192, 384): # White notes:
ctx.setBrush(qtcore.Qt.white)
ctx.drawEllipse(qtcore.QRectF(
x + 0.3 * self.string_height,
y - 0.3 * self.string_height + 0.5,
0.6 * self.string_height,
0.6 * self.string_height,
))
ctx.restore()
if note.base_duration() in (192, 384):
line_y = self.string_id_2_y(string_id)
self._draw_perfect_line(ctx, x + 0.2 * self.string_height, line_y, x + self.string_height, line_y)
alteration = abs(note.value) - string.base_note
if alteration > 6: alteration -= 12
previous = self.note_previous(note)
if previous and (self.partition.song.mesure_at(previous) is self.partition.song.mesure_at(note)):
previous_alteration = abs(previous.value) - string.base_note
if previous_alteration > 6: previous_alteration -= 12
else: previous_alteration = 0
if alteration != previous_alteration:
if alteration == -2: t = "♭♭"
elif alteration == -1: t = "♭"
elif alteration == 0: t = "♮"
elif alteration == 1: t = "♯"
elif alteration == 2: t = "♯♯"
else: t = "?"
ctx.drawText(x - self.canvas.char_h_size * 0.5, y + self.canvas.default_ascent // 2 - 1, t)
if note.is_dotted():
#if string.line_style == 0: ctx.drawEllipse(x + 3.5 * self.string_height, y - 0.2 * self.string_height, 3 * self.scale, 3 * self.scale)
ctx.drawEllipse(x + 1.3 * self.string_height, y - 0.5 * self.string_height, 3 * self.scale, 3 * self.scale)
if note.fx : getattr(self, "draw_note_fx_%s" % note.fx )(ctx, note, string_id)
if note.link_fx: getattr(self, "draw_note_fx_%s" % note.link_fx)(ctx, note, string_id)
def note_width(self, note): return self.string_height
on_touchscreen_new_note = canvas_module.PartitionDrawer.on_touchscreen_new_note
def note_value_possible(self, note, value):
for i, string in enumerate(self.strings): # Search a string with the right tone
if ((string.base_note % 12) == (value % 12)): return 1
return 0
def note_string_id(self, note): return self.partition.view.note_string_id(note)
def draw_stem_and_beam_for_chord(self, ctx, notes, notes_y, previous, next, appo = 0):
if not appo:
canvas_module.TablatureDrawer.draw_stem_and_beam_for_chord(self, ctx, notes, notes_y, previous, next, 0)
_drawers = canvas_module.PartitionDrawer._drawers.copy()
_drawers[288] = _drawers[192] = canvas_module.PartitionDrawer._black
_drawers8 = canvas_module.PartitionDrawer._drawers8.copy()
_drawers8[288] = _drawers8[192] = canvas_module.PartitionDrawer._black
draw_note_fx_link = canvas_module.PartitionDrawer.draw_note_fx_link # No "p" / "h" for pull / hammer
def draw(self, ctx, x, y, width, height, drag = 0):
canvas_module.TablatureDrawer.draw(self, ctx, x, y, width, height, drag)
if not drag:
ctx.setFont(self.canvas.small_font)
for i in range(len(self.strings)):
label = model.note_label(self.strings[i].base_note, 0, self.partition.tonality)
ctx.drawText(self.canvas.start_x - 2.2 * self.canvas.char_h_size, self.string_id_2_y(i) + self.canvas.small_ascent * 0.3, label)
ctx.setFont(self.canvas.default_font)
Songwrite3-0.1/plugins/lyre/__init__.py 0000664 0023421 0023421 00000011700 12702465216 020023 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2009-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import songwrite3.model as model
import songwrite3.plugins as plugins
class LyreView(model.TablatureView):
view_class = "lyre_tablature"
default_instrument = 46
default_icon_filename = "lyre.png"
link_type = model.LINK_NOTES_DEFAULT
automatic_string_id = 1
use_harmonics_for_octavo = 1
def __init__(self, partition, name = "", strings = None):
if partition:
if not hasattr(partition, "let_ring"): partition.let_ring = 1
model.TablatureView.__init__(self, partition, name, strings)
self.tonality = "C"
self.set_tonality(partition.tonality)
def note_string_id(self, note):
return self._calc_string_id(note.value)
def _calc_string_id(self, value):
value = abs(value)
for i, string in enumerate(self.strings): # Search an exact match
if string.base_note == value: return i
value2 = value % 12
for i, string in enumerate(self.strings): # Search a string with the right tone
if (string.base_note % 12) == value2: return i
for i, string in enumerate(self.strings): # Search a string with a #
if string.base_note == value - 1: return i
for i, string in enumerate(self.strings): # Search a string with the right tone and a #
if (string.base_note % 12) == value2 - 1: return i
return len(self.strings) - 1
def get_drawer(self, canvas, compact = False):
from songwrite3.plugins.lyre.drawer import LyreDrawer
return LyreDrawer(canvas, self.partition, compact)
def get_icon_filename(self): return "lyre.png"
def get_type(self):
if len(self.strings) == 6: return model.TablatureView.get_type(self, SixStringLyreView)
else: return model.TablatureView.get_type(self, SevenStringLyreView)
def set_tonality(self, tonality):
# Remove previous tonality first
alterations = model.TONALITIES[self.tonality]
if alterations:
if alterations[0] == model.DIESES[0]: alteration_type = 1 # diese
else: alteration_type = -1 # bemol
else: alteration_type = 0
for string in self.strings:
for alteration in alterations:
if (string.base_note % 12) - alteration_type == alteration:
string.base_note -= alteration_type
break
self.tonality = tonality
alterations = model.TONALITIES[tonality]
if alterations:
if alterations[0] == model.DIESES[0]: alteration_type = 1 # diese
else: alteration_type = -1 # bemol
else: alteration_type = 0
for string in self.strings:
for alteration in alterations:
if (string.base_note % 12) == alteration:
string.base_note += alteration_type
break
def __xml__(self, xml = None, context = None): model.TablatureView.__xml__(self, xml, context, type = "lyre")
_LYRE_STRING_COLORS = [
"red" , # 0
"red" , # 1
"black", # 2
"black", # 3
"black", # 4
"blue" , # 5
"blue" , # 6
"black", # 7
"black", # 8
"black", # 9
"black", # 10
"black", # 11
]
class LyreString(model.TablatureString):
def color(self): return _LYRE_STRING_COLORS[self.base_note % 12]
model.LyreString = LyreString
class SixStringLyreView(LyreView):
def __init__(self, partition, name = ""):
super(SixStringLyreView, self).__init__(partition, name or _("Lyre"), self.new_strings())
@classmethod
def new_strings(Class):
return[LyreString(64, -1), LyreString(62, -1), LyreString(60, -1), LyreString(59, -1), LyreString(57, -1), LyreString(55, -1)]
class SevenStringLyreView(LyreView):
def __init__(self, partition, name = ""):
super(SevenStringLyreView, self).__init__(partition, name or _("Lyre"), self.new_strings())
@classmethod
def new_strings(Class):
return[LyreString(64, -1), LyreString(62, -1), LyreString(60, -1), LyreString(59, -1), LyreString(57, -1), LyreString(55, -1), LyreString(53, -1)]
plugins.ViewPlugin(LyreView , LyreString, "lyre" , None)
plugins.ViewPlugin(SixStringLyreView , LyreString, "six_string_lyre" , "tab")
plugins.ViewPlugin(SevenStringLyreView, LyreString, "seven_string_lyre", "tab")
Songwrite3-0.1/plugins/__init__.py 0000664 0023421 0023421 00000011647 12727252446 017070 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2007-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, os, os.path
import songwrite3.globdef as globdef
import songwrite3.model as model
PLUGINS = {}
class Plugin(object):
def __init__(self, name):
print("Registering %s plugin..." % name, file = sys.stderr)
self.name = name
PLUGINS[name] = self
def create_ui(self, app):
pass
EXPORT_FORMATS = []
class ExportPlugin(Plugin):
def __init__(self, format, exts, can_export_song = 1, can_export_songbook = 0):
self.format = format
self.exts = exts
self.can_export_song = can_export_song
self.can_export_songbook = can_export_songbook
EXPORT_FORMATS.append(format)
Plugin.__init__(self, "%s exporter" % format.lower())
def create_ui(self, app):
if self.can_export_song:
app.add_to_menu(app.export_menu, 0, _("Export to %s") % self.format + "..." * int(globdef.config.ASK_FILENAME_ON_EXPORT), self.on_menu_click, arg = app)
if self.can_export_songbook:
app.add_to_menu(app.export_songbook_menu, 0, _("Export songbook to %s") % self.format + "..." * int(globdef.config.ASK_FILENAME_ON_EXPORT), self.on_songbook_menu_click, arg = app)
def on_menu_click(self, widget, app):
if globdef.config.ASK_FILENAME_ON_EXPORT or (not app.filename):
filename = app.prompt_save_filename(self.exts, self.format)
else:
filename = app.filename.replace(".sw.xml", "") + "." + self.exts[0]
if filename: self.export_to(app.song, filename)
def on_songbook_menu_click(self, widget, app):
if globdef.config.ASK_FILENAME_ON_EXPORT or (not app.songbook.filename):
filename = app.prompt_save_filename(self.exts, self.format)
else:
filename = app.songbook.filename.replace(".sw.xml", "") + "." + self.exts[0]
if filename: self.export_to(app.songbook, filename)
def export_to(self, song, filename):
data = self.export_to_string(song)
if isinstance(data, bytes):
if filename == "-": sys.stdout.buffer.write(data)
else: open(filename, "wb").write(data)
else:
if filename == "-": print(data)
else: open(filename, "w").write(data)
def export_to_string(self, song): pass
IMPORT_FORMATS = []
class ImportPlugin(Plugin):
def __init__(self, format, exts, binary = False):
self.format = format
self.exts = exts
self.binary = binary
IMPORT_FORMATS.append(format)
Plugin.__init__(self, "%s importer" % format.lower())
def create_ui(self, app):
app.add_to_menu(app.import_menu, 0, _("Import from %s") % self.format + "...", self.on_menu_click, arg = app)
def on_menu_click(self, widget, app):
if app.check_save(): return
filename = app.prompt_open_filename(self.exts, self.format)
if filename:
song = self.import_from(filename)
app.set_song(song)
def import_from(self, filename):
if filename == "-":
return self.import_from_string(sys.stdin.read())
else:
if self.binary: s = open(filename, "rb").read()
else: s = open(filename ).read()
return self.import_from_string(s)
def import_from_string(self, data): pass
PLUGINS_VIEWS = {}
class ViewPlugin(Plugin):
def __init__(self, View, String, code_name, category):
PLUGINS_VIEWS[code_name] = View, String
Plugin.__init__(self, "%s view" % code_name)
if category: model.VIEWS[category].append(View)
def create_plugins_ui(app):
for plugin in PLUGINS.values(): plugin.create_ui(app)
def load_all_plugins():
plugins_dir = os.path.dirname(__file__)
for file in [
"songwrite",
"abc",
"texttab",
"midi",
"pdf",
"fingering",
"additional_instruments",
"lyre",
"chord",
"accordion",
]:
try:
__import__("songwrite3.plugins.%s" % file)
except:
print("Error while loading plugin 'songwrite3.plugins.%s'!" % file, file = sys.stderr)
sys.excepthook(*sys.exc_info())
print(file = sys.stderr)
def get_importer(format):
return PLUGINS["%s importer" % format.lower()]
def get_exporter(format):
return PLUGINS["%s exporter" % format.lower()]
def load_view(partition, attrs):
View, String = PLUGINS_VIEWS[attrs["type"]]
view = View(partition, "")
view.load_attrs(attrs)
return view, String
Songwrite3-0.1/main.py 0000664 0023421 0023421 00000150207 12727260653 014567 0 ustar jiba jiba 0000000 0000000 # -*- coding: utf-8 -*-
# Songwrite 3
# Copyright (C) 2001-2016 Jean-Baptiste LAMY -- jibalamy@free.fr
#
# This program 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.
#
# This program 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 this program. If not, see .
import sys, time, getpass, os, os.path, locale, unicodedata, subprocess
from io import StringIO
import PyQt5.QtCore as qtcore
import PyQt5.QtWidgets as qtwidgets
import PyQt5.QtGui as qtgui
import editobj3, editobj3.undoredo as undoredo, editobj3.introsp as introsp, editobj3.field as field, editobj3.editor as editor, editobj3.editor_qt as editor_qt
from editobj3.observe import *
import songwrite3, songwrite3.globdef as globdef, songwrite3.model as model, songwrite3.player as player
import songwrite3.__editobj3__
import songwrite3.midi
editor_qt.MENU_LABEL_2_IMAGE.update({
"Add..." : "list-add",
"Remove" : "list-remove",
"Move up" : "go-up",
"Move down" : "go-down",
"Play from the beginning" : "media-playback-start",
"Play from the beginning in loop" : "media-playback-start",
"Play from here" : "media-playback-start",
"Play from here in loop" : "media-playback-start",
"Stop playing" : "media-playback-stop",
"Edit..." : "document-properties",
"Songbook properties..." : "document-properties",
"Song and instruments properties..." : "document-properties",
"Instrument properties..." : "document-properties",
"Note properties..." : "document-properties",
"Save songbook as..." : "document-save-as",
"preview_print" : "document-print",
"preview_print_songbook" : "document-print",
"New window..." : "window-new",
"New song" : "document-new",
"New songbook" : "document-new",
"Select all" : "edit-select-all",
"Paste last selection" : "edit-paste",
"About..." : "help-about",
"Manual..." : "help-contents",
})
from songwrite3.canvas import Canvas
app = qtwidgets.QApplication(sys.argv)
mainloop = app.exec
app.setWindowIcon(qtgui.QIcon(os.path.join(globdef.DATADIR, "songwrite3_64x64.png")))
def remove_accents(s): return unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode("ascii")
APPS = {}
class App(editor_qt.QtBaseDialog):
def __init__(self, song = None):
self.init_window(False)
self.undo_stack = undoredo.Stack(globdef.config.NUMBER_OF_UNDO)
self.updating_menu = False
self.partition_editor = None
self.mesure_editor = None
self.note_editor = None
self.filename = None
self.build_default_menubar()
#file_menu = self.add_to_menu(self.menubar, 1, "File", self.on_file_menu)
file_menu = self.file_menu
self.add_to_menu(file_menu, 0, "New window..." , self.on_new_app , pos = 0)
self.add_to_menu(file_menu, 0, "New song" , self.on_new , pos = 1, accel = "C-N")
self.add_to_menu(file_menu, 0, "New songbook" , self.on_new_songbook, pos = 2)
self.add_to_menu(file_menu, 0, "Open..." , self.on_open , pos = 3, accel = "C-O")
self.add_to_menu(file_menu, 0, "Save" , self.on_save , pos = 4, accel = "C-S")
self.add_to_menu(file_menu, 0, "Save as..." , self.on_save_as , pos = 5, accel = "C-S-S")
self.save_as_songbook_menu = self.add_to_menu(file_menu, 0, "Save songbook as...", self.on_save_as_songbook, pos = 6)
self.add_separator_to_menu(file_menu, pos = 7)
self.import_menu = self.add_to_menu(file_menu, 1, "Import", pos = 8)
self.export_menu = self.add_to_menu(file_menu, 1, "Export", pos = 9)
self.export_songbook_menu = self.add_to_menu(file_menu, 1, "Export songbook", pos = 10)
self.add_to_menu(file_menu, 0, "preview_print", self.on_preview_print, pos = 11)
self.preview_print_songbook_menu = self.add_to_menu(file_menu, 0, "preview_print_songbook", self.on_preview_print_songbook, pos = 12)
self.add_separator_to_menu(file_menu, pos = 13)
#self.add_to_menu(file_menu, 0, "Close", self.on_close)
self.add_separator_to_menu(file_menu)
for file in globdef.config.PREVIOUS_FILES:
self.add_to_menu(file_menu, 0, file, lambda obj, file = file: self.open_filename(file))
#edit_menu = self.add_to_menu(self.menubar, 1, "Edit", self.on_edit_menu)
edit_menu = self.edit_menu
#self.undo_menu = self.add_to_menu(edit_menu, 0, "Undo", self.on_undo, accel = "C-Z")
#self.redo_menu = self.add_to_menu(edit_menu, 0, "Redo", self.on_redo, accel = "C-Y")
self.add_separator_to_menu(edit_menu)
self.add_to_menu(edit_menu, 0, "Select all" , self.on_select_all, accel = "C-A", accel_enabled = 0) # Disabled these accels,
self.add_to_menu(edit_menu, 0, "Cut" , self.on_note_cut , accel = "C-X", accel_enabled = 0) # because they block C-A, C-V,... in lyrics editor
self.add_to_menu(edit_menu, 0, "Copy" , self.on_note_copy , accel = "C-C", accel_enabled = 0)
self.add_to_menu(edit_menu, 0, "Paste" , self.on_note_paste, accel = "C-V", accel_enabled = 0)
self.add_to_menu(edit_menu, 0, "Insert/remove beats..." , self.on_insert_times)
self.add_to_menu(edit_menu, 0, "Insert/remove bars..." , self.on_insert_bars)
self.add_separator_to_menu(edit_menu)
self.add_to_menu(edit_menu, 0, "Rhythm and bars..." , self.on_bars_prop)
self.add_to_menu(edit_menu, 0, "Repeats and playlist...", self.on_playlist_prop)
self.add_separator_to_menu(edit_menu)
self.add_to_menu(edit_menu, 0, "Preferences...", self.on_preferences)
play_menu = self.add_to_menu(self.menubar, 1, "Play")
self.add_to_menu(play_menu, 0, "Play from the beginning" , self.on_play)
self.add_to_menu(play_menu, 0, "Play from here" , self.on_play_from_here, accel = " ")
self.add_to_menu(play_menu, 0, "Play from the beginning in loop", self.on_play_in_loop)
self.add_to_menu(play_menu, 0, "Play from here in loop" , self.on_play_from_here_in_loop)
self.add_to_menu(play_menu, 0, "Stop playing" , self.on_stop_playing)
self.book_menu = self.add_to_menu(self.menubar, 1, "Songbook")
self.add_to_menu(self.book_menu, 0, "New songbook" , self.on_new_songbook)
self.add_separator_to_menu(self.book_menu)
self.add_to_menu(self.book_menu, 0, "Songbook properties..." , self.on_songbook_prop)
self.add_separator_to_menu(self.book_menu)
#self.no_songbook_menu = self.add_to_menu(book_menu, 0, "(no songbook opened)")
#self.set_menu_enable(self.no_songbook_menu, False)
self.songbook_songs_menu = []
song_menu = self.add_to_menu(self.menubar, 1, "Song")
self.add_to_menu(song_menu, 0, "New song" , self.on_new)
self.add_separator_to_menu(song_menu)
self.add_to_menu(song_menu, 0, "Song and instruments properties...", self.on_song_prop)
instru_menu = self.add_to_menu(self.menubar, 1, "Instrument")
self.add_to_menu(instru_menu, 0, "Add..." , self.on_add_instrument)
self.add_to_menu(instru_menu, 0, "Duplicate" , self.on_dupplicate_instrument)
self.add_to_menu(instru_menu, 0, "Remove" , self.on_remove_instrument)
self.add_to_menu(instru_menu, 0, "Move up" , self.on_move_instrument_up)
self.add_to_menu(instru_menu, 0, "Move down" , self.on_move_instrument_down)
self.add_separator_to_menu(instru_menu)
self.add_to_menu(instru_menu, 0, "Instrument properties..." , self.on_instrument_prop)
note_menu = self.add_to_menu(self.menubar, 1, "Note")
self.add_to_menu(note_menu, 0, "One octavo above", lambda *args: self.canvas and self.canvas.shift_selections_values( 12))
self.add_to_menu(note_menu, 0, "One pitch above" , lambda *args: self.canvas and self.canvas.shift_selections_values( 1), accel = "+", accel_enabled = 0)
self.add_to_menu(note_menu, 0, "One pitch below" , lambda *args: self.canvas and self.canvas.shift_selections_values(-1), accel = "-", accel_enabled = 0)
self.add_to_menu(note_menu, 0, "One octavo below", lambda *args: self.canvas and self.canvas.shift_selections_values(-12))
self.add_separator_to_menu(note_menu)
self.add_to_menu(note_menu, 0, "Delete" , lambda *args: self.canvas and self.canvas.delete_notes(self.canvas.selections), accel = "Del", accel_enabled = 0)
self.add_to_menu(note_menu, 0, "Arrange notes at fret...", self.on_note_arrange)
self.add_to_menu(note_menu, 0, "Note properties..." , self.on_note_prop)
for duration in [384, 192, 96, 48, 24, 12]:
def f(event, duration = duration):
if self.updating_menu or not self.canvas: return
if self.canvas.selections:
self.canvas.current_duration = duration
notes = list(self.canvas.selections)
old_durations = [note.duration for note in notes]
def do_it(notes = notes):
for note in notes:
dotted = note.is_dotted()
triplet = note.is_triplet()
note.duration = duration
if dotted : note.duration *= 1.5
if triplet: note.duration = (note.duration * 2) // 3
def undo_it(notes = notes):
for i in range(len(notes)): notes[i] = old_durations[i]
editobj3.undoredo.UndoableOperation(do_it, undo_it, _("change of %s") % _("duration"), self.undo_stack)
setattr(self, "set_duration_%s" % duration, f)
def toggle_dotted(*args):
if self.canvas and not self.updating_menu: self.canvas.toggle_dotted()
def toggle_triplet(*args):
if self.canvas and not self.updating_menu: self.canvas.toggle_triplet()
def toggle_accent(*args):
if self.canvas and not self.updating_menu: self.canvas.toggle_accent()
self.toggle_dotted = toggle_dotted
self.toggle_triplet = toggle_triplet
self.toggle_accent = toggle_accent
def on_duration_menu_cliked(*args):
self.updating_menu = True
if self.canvas and self.canvas.selections:
note = tuple(self.canvas.selections)[0]
self.set_menu_checked(dotted , note.is_dotted ())
self.set_menu_checked(triplet, note.is_triplet())
self.updating_menu = False
duration_menu = self.add_to_menu(self.menubar, 1, "Duration", on_duration_menu_cliked)
self.add_to_menu(duration_menu, 0, "Whole" , self.set_duration_384, image = self.menu_image("note_384.png"))
self.add_to_menu(duration_menu, 0, "Half" , self.set_duration_192, image = self.menu_image("note_192.png"))
self.add_to_menu(duration_menu, 0, "Quarter" , self.set_duration_96 , image = self.menu_image("note_96.png"))
self.add_to_menu(duration_menu, 0, "Eighth" , self.set_duration_48 , image = self.menu_image("note_48.png"))
self.add_to_menu(duration_menu, 0, "Sixteenth" , self.set_duration_24 , image = self.menu_image("note_24.png"))
self.add_to_menu(duration_menu, 0, "Thirty-second", self.set_duration_12 , image = self.menu_image("note_12.png"))
self.add_separator_to_menu(duration_menu)
dotted = self.add_to_menu(duration_menu, 0, "Dotted" , toggle_dotted , type = "check", accel = ".", accel_enabled = 0)
triplet = self.add_to_menu(duration_menu, 0, "Triplet" , toggle_triplet, type = "check")
self.add_separator_to_menu(duration_menu)
self.add_to_menu(duration_menu, 0, "Longer" , self.set_duration_longer , accel = "*")
self.add_to_menu(duration_menu, 0, "Shorter" , self.set_duration_shorter, accel = "/")
def on_volume_fx_menu_cliked(*args):
self.updating_menu = True
if self.canvas and self.canvas.selections:
note = tuple(self.canvas.selections)[0]
if note.volume == 255: self.set_menu_checked(volume_menus[0], True)
else: self.set_menu_checked(volume_menus[0], False)
if 190 < note.volume < 255: self.set_menu_checked(volume_menus[1], True)
else: self.set_menu_checked(volume_menus[1], False)
if 100 < note.volume <= 190: self.set_menu_checked(volume_menus[2], True)
else: self.set_menu_checked(volume_menus[2], False)
if note.volume <= 100: self.set_menu_checked(volume_menus[3], True)
else: self.set_menu_checked(volume_menus[3], False)
fxs = set([note.link_fx, note.duration_fx, note.strum_dir_fx])
if note.fx == "bend": fxs.add("bend%s" % note.bend_pitch)
else: fxs.add(note.fx)
for key, menu in fx_menus.items():
self.set_menu_checked(menu, key in fxs)
self.updating_menu = False
for volume in [255, 204, 120, 50]:
def f(event, volume = volume):
if self.canvas and not self.updating_menu: self.canvas.set_selections_volume(volume)
setattr(self, "set_volume_%s" % volume, f)
for type, fxs in model.ALL_FXS:
if type: type += "_"
for fx in fxs:
if not fx: continue
for arg in model.FXS_ARGS.get(fx, [None]):
def f(event, type = type, fx = fx, arg = arg):
if self.canvas and not self.updating_menu: getattr(self.canvas, "set_selections_%sfx" % type)(fx, arg)
if arg: setattr(self, "set_fx_%s%s" % (fx, str(arg).replace(".", "")), f)
else: setattr(self, "set_fx_%s" % fx, f)
effect_menu = self.add_to_menu(self.menubar, 1, "Effect", on_volume_fx_menu_cliked)
volume_menus = [
self.add_to_menu(effect_menu, 0, "Accentuated" , self.set_volume_255, type = "radio", accel = "Enter", accel_enabled = 0),
self.add_to_menu(effect_menu, 0, "Normal" , self.set_volume_204, type = "radio"),
self.add_to_menu(effect_menu, 0, "Low" , self.set_volume_120, type = "radio"),
self.add_to_menu(effect_menu, 0, "Very low" , self.set_volume_50 , type = "radio"),
]
self.add_separator_to_menu(effect_menu)
self.add_to_menu(effect_menu, 0, "Remove all effects" , self.set_fx_none , accel = "n", accel_enabled = 0)
self.add_separator_to_menu(effect_menu)
fx_menus = self.fx_menus = {
"bend0.5" : self.add_to_menu(effect_menu, 0, "bend 0.5" , self.set_fx_bend05 , type = "check", accel = "b", accel_enabled = 0),
"bend1.0" : self.add_to_menu(effect_menu, 0, "bend 1" , self.set_fx_bend10 , type = "check"),
"bend1.5" : self.add_to_menu(effect_menu, 0, "bend 1.5" , self.set_fx_bend15 , type = "check"),
"tremolo" : self.add_to_menu(effect_menu, 0, "tremolo" , self.set_fx_tremolo , type = "check", accel = "t", accel_enabled = 0),
"dead" : self.add_to_menu(effect_menu, 0, "dead note", self.set_fx_dead , type = "check", accel = "d", accel_enabled = 0),
"roll" : self.add_to_menu(effect_menu, 0, "roll" , self.set_fx_roll , type = "check", accel = "r", accel_enabled = 0),
"harmonic": self.add_to_menu(effect_menu, 0, "harmonic" , self.set_fx_harmonic, type = "check", accel = "h", accel_enabled = 0),
}
self.add_separator_to_menu(effect_menu)
fx_menus.update({
"appoggiatura": self.add_to_menu(effect_menu, 0, "appoggiatura", self.set_fx_appoggiatura, type = "check", accel = "a", accel_enabled = 0),
"fermata" : self.add_to_menu(effect_menu, 0, "fermata" , self.set_fx_fermata , type = "check", accel = "p", accel_enabled = 0),
"breath" : self.add_to_menu(effect_menu, 0, "breath" , self.set_fx_breath , type = "check"),
})
self.add_separator_to_menu(effect_menu)
fx_menus.update({
"link" : self.add_to_menu(effect_menu, 0, "link" , self.set_fx_link , type = "check", accel = "l", accel_enabled = 0),
"slide" : self.add_to_menu(effect_menu, 0, "slide", self.set_fx_slide , type = "check", accel = "s", accel_enabled = 0),
})
self.add_separator_to_menu(effect_menu)
fx_menus.update({
"up" : self.add_to_menu(effect_menu, 0, "up" , self.set_fx_up , type = "check", accel = "^", accel_enabled = 0),
"down" : self.add_to_menu(effect_menu, 0, "down" , self.set_fx_down , type = "check", accel = "_", accel_enabled = 0),
"down_thumb" : self.add_to_menu(effect_menu, 0, "down_thumb", self.set_fx_down_thumb, type = "check", accel = ",", accel_enabled = 0),
})
help_menu = self.add_to_menu(self.menubar, 1, "Help")
self.add_to_menu(help_menu, 0, "About..." , self.on_about)
self.add_to_menu(help_menu, 0, "Manual..." , self.on_manual, accel = "F1")
self.add_separator_to_menu(help_menu)
self.add_to_menu(help_menu, 0, "Dump" , self.on_dump)
self.add_to_menu(help_menu, 0, "GC" , self.on_gc)
songwrite3.plugins.create_plugins_ui(self)
self.song = None
self.songbook = None
self.instrument_chooser_pane = None
self.paned = None
self.scrolled = None
self.canvas = None
self.selected_partition = None
self.selected_mesure = None
self.selected_note = None
if isinstance(song, model.Song) : self.set_songbook(None); self.set_song(song)
elif isinstance(song, model.Songbook): self.set_songbook(song)
else: self.set_song (None)
self.window.closeEvent = self.closeEvent
self.destroy = self.window.destroy
toolbar = self.toolbar = qtwidgets.QToolBar()
self.add_to_menu(toolbar, 0, "Save", self.on_save)
self.add_to_menu(toolbar, 0, "preview_print", self.on_preview_print)
self.add_to_menu(toolbar, 0, "Play from here", self.on_play_from_here)
self.add_to_menu(toolbar, 0, "Stop playing", self.on_stop_playing)
self.toolbar.addSeparator()
radio_group = qtwidgets.QActionGroup(self.window)
for duration in [384, 192, 96, 48, 24, 12]:
action = self.add_to_menu(toolbar, 0, model.DURATIONS[duration], getattr(self, "set_duration_%s" % duration), image = self.menu_image("note_%s.png" % duration))
action.setCheckable(True)
radio_group.addAction(action)
setattr(self, "toolbar_duration_%s" % duration, action)
self.toolbar_dotted = self.add_to_menu(toolbar, 0, "Dotted" , self.toggle_dotted , image = self.menu_image("note_144.png"))
self.toolbar_triplet = self.add_to_menu(toolbar, 0, "Triplet", self.toggle_triplet, image = self.menu_image("note_64.png" ))
self.toolbar_dotted .setCheckable(True)
self.toolbar_triplet.setCheckable(True)
self.toolbar.addSeparator()
self.toolbar_accent = self.add_to_menu(toolbar, 0, "Accentuated", self.toggle_accent, image = self.menu_image("effet_accent.png" ))
self.toolbar_accent.setCheckable(True)
self.toolbar_buttons = {}
for type, fxs in model.ALL_FXS:
for fx in fxs:
if not fx: continue
if fx == "bend": func = self.set_fx_bend05
else: func = getattr(self, "set_fx_%s" % fx)
self.toolbar_buttons[fx] = action = self.add_to_menu(toolbar, 0, fx, func, image = self.menu_image("effet_%s.png" % fx))
action.setCheckable(True)
zoom_levels = [0.25, 0.35, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0]
def zoom_in (menu_item):
if self.canvas: self.canvas.change_zoom( 1)
def zoom_out(menu_item):
if self.canvas: self.canvas.change_zoom(-1)
def set_zoom(zoom):
if self.canvas:
zoom = int(zoom.replace("%", "")) / 100.0
self.canvas.set_zoom(zoom)
self.on_set_zoom = set_zoom
zoom_toolbar = qtwidgets.QToolBar()
zoom_out_menu = self.add_to_menu(zoom_toolbar, 0, " ‒ ", zoom_out)
self.zoom_menu = qtwidgets.QComboBox()
for zoom in zoom_levels:
self.zoom_menu.addItem("%s%%" % int(zoom * 100.0), zoom)
self.zoom_menu.setFrame(False)
self.zoom_menu.setCurrentText("100%")
self.zoom_menu.currentTextChanged.connect(set_zoom)
zoom_toolbar.addWidget(self.zoom_menu)
zoom_in_menu = self.add_to_menu(zoom_toolbar, 0, " + ", zoom_in)
self.toolbar_layout = qtwidgets.QHBoxLayout()
self.toolbar_layout.addWidget(toolbar, 1)
self.toolbar_layout.addWidget(zoom_toolbar)
self.layout.insertLayout(1, self.toolbar_layout)
if globdef.config.WINDOW_WIDTH == 0:
self.window.showMaximized()
else:
self.window.resize(globdef.config.WINDOW_WIDTH, globdef.config.WINDOW_HEIGHT)
self.window.showNormal()
if globdef.config.WINDOW_X != -1:
self.window.move(globdef.config.WINDOW_X, globdef.config.WINDOW_Y)
def create_content(self):
self.window.setWindowTitle("Songwrite3")
self.instrument_chooser_pane = InstrumentChooserPane(self)
self.window.layout().addWidget(self.instrument_chooser_pane)
def menu_image(self, filename):
icon = qtgui.QIcon(os.path.join(globdef.DATADIR, filename))
return icon
def set_fx_none(self, *args): self.canvas.remove_selections_fx()
def set_selected_partition(self, partition):
self.selected_partition = partition
if self.partition_editor and partition:
if self.partition_editor.window.isVisible(): self.partition_editor.edit(partition)
else: self.partition_editor = None
def set_selected_mesure(self, mesure):
self.selected_mesure = mesure
if self.mesure_editor and mesure:
if self.mesure_editor.window.isVisible(): self.mesure_editor.edit(mesure)
else: self.mesure_editor = None
def set_selected_note(self, note):
self.updating_menu = True
self.selected_note = note
if self.note_editor and note:
if self.note_editor.window.isVisible(): self.note_editor.edit(note)
else: self.note_editor = None
if isinstance(note, introsp.ObjectPack):
mesure_ids = set([self.song.mesure_at(n).get_number() + 1 for n in note.objects])
if len(mesure_ids) == 1:
mesure_label = self.song.mesure_at(note.objects[0])
else:
mesure_label = _("Bars #%s to #%s") % (min(mesure_ids), max(mesure_ids))
if len(note.objects) == 2:
delta = abs(note.objects[0].value - note.objects[1].value)
note_label = _("__interval__%s" % delta)
if note_label.startswith("_"): note_label = "2 %s" % _("notes")
else:
note_label = "%s %s" % (len(note.objects), _("notes"))
note = note.objects[-1]
else:
mesure_label = self.song.mesure_at(note)
note_label = str(note)
current_duration = note.base_duration()
self.set_menu_checked(getattr(self, "toolbar_duration_%s" % current_duration), True)
self.set_menu_checked(self.toolbar_dotted , note.is_dotted())
self.set_menu_checked(self.toolbar_triplet, note.is_triplet())
self.set_menu_checked(self.toolbar_accent , note.volume == 255)
fxs = { note.fx, note.link_fx, note.duration_fx, note.strum_dir_fx }
for key, button in self.toolbar_buttons.items(): self.set_menu_checked(button, key in fxs)
self.window.setWindowTitle("Songwrite3 -- %s -- %s -- %s" % (self.song.title, mesure_label, note_label))
self.updating_menu = False
def get_selected_notes(self):
if isinstance(self.selected_note, introsp.ObjectPack): return self.selected_note.objects
return [self.selected_note]
def partition_categories(self):
next_strophe_number = len([lyrics for lyrics in self.song.partitions if isinstance(lyrics, model.Lyrics) and _("Strophe #%s").replace("%s", "") in lyrics.header]) + 1
return PartitionCategory(_("Available score views"), _("Select the score and score view you want to add."),
[model.Partition(None, view_type.default_instrument, view_type) for view_type in [model.GuitarView, model.BassView]] +
[PartitionCategory(_("More tablatures..."), _("Tablature (for guitar-like instruments)"),
[model.Partition(None, view_type.default_instrument, view_type) for view_type in model.VIEWS["tab"] if not view_type is model.GuitarView and not view_type is model.BassView])
] +
[model.Partition(None, view_type.default_instrument, view_type) for view_type in [model.PianoView, model.GuitarStaffView, model.VocalsView]] +
[PartitionCategory(_("More staffs..."), _("Staff (for any instrument)"),
[model.Partition(None, view_type.default_instrument, view_type) for view_type in model.VIEWS["staff"] if not view_type is model.PianoView and not view_type is model.VocalsView and not view_type is model.GuitarStaffView])
] +
[model.Partition(None, model.TomView.default_instrument, model.TomView)] +
[PartitionCategory(_("More drums..."), _("Drum tablatures (for drums)"),
[model.Partition(None, view_type.default_instrument, view_type) for view_type in model.VIEWS["drums"] if not view_type is model.TomView])
] +
[
model.Lyrics(None, header = _("Chorus")),
model.Lyrics(None, header = _("Strophe #%s") % next_strophe_number),
])
def edit(self, o, *args, **kargs):
return editobj3.edit(o, undo_stack = self.undo_stack, master = self, menubar = False, *args, **kargs)
def on_new_app(self, *args): self.__class__()
def close_dialog(self, *args): self.closeEvent(None)
def closeEvent(self, e):
if self.check_save():
if e: e.ignore()
return
if e: e.accept()
if self.song: del APPS[self.song]
if self.songbook: del APPS[self.songbook]
self.destroy()
if len(APPS) == 0:
globdef.config.WINDOW_X = self.window.x()
globdef.config.WINDOW_Y = self.window.y()
if self.window.isMaximized():
globdef.config.WINDOW_WIDTH = globdef.config.WINDOW_HEIGHT = 0
else:
globdef.config.WINDOW_WIDTH = self.window.width()
globdef.config.WINDOW_HEIGHT = self.window.height()
globdef.config.save()
sys.exit()
def prompt_open_filename(self, exts = [".sw.xml"], typename = "", title = None):
ext_label = self.get_ext_label(exts, typename)
if self.filename: dirname = self.filename
else: dirname = ""
filename = qtwidgets.QFileDialog.getOpenFileName(self.window, title or _("Open..."), dirname, ext_label)[0]
return filename
def prompt_save_filename(self, exts = [".sw.xml"], typename = "", title = None):
ext_label = self.get_ext_label(exts, typename)
if self.filename:
filename = self.filename.split(".")[0] + exts[0]
else:
filename = remove_accents(self.song.title.lower().replace(" ", "_").replace("'", "").replace("/", "_")) + exts[0]
filename = qtwidgets.QFileDialog.getSaveFileName(self.window, title or _("Save as..."), filename, ext_label)[0]
if filename:
for ext in exts:
if filename.endswith(ext): break
else:
filename += exts[0]
return filename
def get_ext_label(self, exts, typename):
if exts == [".sw.xml"]:
ext_label = _("__songwrite_file_format__") + " (*.sw.xml)"
else:
exts_joined = " ".join("*%s" % ext for ext in exts)
typename = typename or exts_joined
ext_label = "%s %s (%s)" % (_("Files"), typename, exts_joined)
return ext_label + ";;%s (*.*)" % _("__all_files__")
def on_open(self, event = None):
if self.check_save(): return
filename = self.prompt_open_filename()
if filename:
self.open_filename(filename, do_check_save = 0) # check_save has already been done before asking the filename
def check_save(self):
if not self.song: return False
if self.undo_stack.undoables == self.last_undoables: return 0 # The undoable actions have not changed => nothing to save.
msg = qtwidgets.QMessageBox()
msg.setText(_("Save modifications before closing?"))
#msg.setStandardButtons(qtwidgets.QMessageBox.Save | qtwidgets.QMessageBox.Discard | qtwidgets.QMessageBox.Cancel)
#msg.setDefaultButton(qtwidgets.QMessageBox.Cancel)
save_button = msg.addButton(_("Save"), 0)
discard_button = msg.addButton(_("Close without saving"), 0)
cancel_button = msg.addButton(_("Cancel"), 0)
msg.setDefaultButton(cancel_button)
msg.setEscapeButton(cancel_button)
ret = msg.exec()
if ret == 1: return False
elif ret == 2: return True
elif ret == 0:
self.on_save()
return self.check_save() # The user may have canceled the "save as" dialog box !
def set_song(self, song):
if self.song:
del APPS[self.song]
if self.filename: model.unload_song(self.filename)
self.undo_stack.clear()
self.last_undoables = []
self.song = song
if self.instrument_chooser_pane:
self.instrument_chooser_pane.hide()
self.window.layout().addWidget(self.instrument_chooser_pane)
self.instrument_chooser_pane.destroy()
if self.canvas:
zoom = self.canvas.zoom
self.canvas.hide()
self.window.layout().addWidget(self.canvas)
self.canvas.destroy()
else:
zoom = 1.0
if song:
APPS[song] = self
self.filename = song.filename
self.window.setWindowTitle("Songwrite3 -- %s" % song.title)
self.canvas = Canvas(self, self.song, zoom)
self.window.layout().addWidget(self.canvas)
else:
self.window.setWindowTitle("Songwrite3")
self.instrument_chooser_pane = InstrumentChooserPane(self)
self.window.layout().addWidget(self.instrument_chooser_pane)
def set_songbook(self, songbook):
if self.songbook:
del APPS[self.songbook]
unobserve(self.songbook.song_refs, self.on_songbook_changed)
self.songbook = songbook
if self.songbook:
APPS[self.songbook] = self
if self.songbook.song_refs: self.set_song(self.songbook.song_refs[0].get_song())
observe(self.songbook.song_refs, self.on_songbook_changed)
self.rebuild_songbook_menu()
def rebuild_songbook_menu(self):
for menu in self.songbook_songs_menu: self.book_menu.removeAction(menu)
if self.songbook:
self.songbook_songs_menu = []
for song_ref in self.songbook.song_refs:
def open_song(event, song_ref = song_ref):
self.set_song(song_ref.get_song())
menu = self.add_to_menu(self.book_menu, 0, song_ref.title, open_song)
self.songbook_songs_menu.append(menu)
else:
no_songbook_menu = self.add_to_menu(self.book_menu, 0, "(no songbook opened)")
self.set_menu_enable(no_songbook_menu, False)
self.songbook_songs_menu = [no_songbook_menu]
def on_songbook_changed(self, obj, type, new, old):
self.rebuild_songbook_menu()
def on_file_menu(self, *args):
if self.songbook:
self.set_menu_enable(self.save_as_songbook_menu , 1)
self.set_menu_enable(self.preview_print_songbook_menu, 1)
self.set_menu_enable(self.export_songbook_menu , 1)
else:
self.set_menu_enable(self.save_as_songbook_menu , 0)
self.set_menu_enable(self.preview_print_songbook_menu, 0)
self.set_menu_enable(self.export_songbook_menu , 0)
def on_new(self, *args):
if self.check_save(): return
self.filename = None
self.set_songbook(None)
self.set_song (None)
def on_new_songbook(self, *args):
if self.check_save(): return
songbook = model.Songbook()
songbook.authors = getpass.getuser().title()
songbook.title = _("%s's songbook") % songbook.authors
self.set_songbook(songbook)
self.on_songbook_prop()
def on_open(self, event = None):
if self.check_save(): return
filename = self.prompt_open_filename()
if not filename: return
self.open_filename(filename, do_check_save = 0) # check_save has already been done before asking the filename
def open_filename(self, filename, do_check_save = 1):
globdef.config.LAST_DIR = os.path.dirname(filename)
if do_check_save and self.check_save(): return
if not os.path.exists(filename):
editobj3.edit(_("File does not exist!"), on_validate = lambda o: None)
else:
self.filename = filename
song = model.get_song(filename)
if isinstance(song, model.Song):
self.set_songbook(None)
self.set_song (song)
else:
self.set_songbook(song)
self.on_songbook_prop()
globdef.config.add_previous_file(filename)
def on_save_as(self, event = None):
filename = self.prompt_save_filename()
if not filename: return
self.filename = self.song.filename = filename
self.on_save(save_song = 1, save_songbook = 0)
def on_save_as_songbook(self, event = None):
filename = self.prompt_save_filename()
if not filename: return
if not filename.endswith(".sw.xml"): filename += ".sw.xml"
self.songbook.set_filename(filename)
self.on_save(save_song = 0, save_songbook = 1)
def on_save(self, event = None, save_song = 1, save_songbook = 1):
if not self.song: return
if save_song:
ok_song = 0
if not self.filename: self.on_save_as()
if self.filename:
#self.song.__xml__(codecs.lookup("utf8")[3](open(self.filename, "w")))
s = StringIO()
self.song.__xml__(s)
s = s.getvalue()
open(self.filename, "w").write(s)
ok_song = 1
if not self.songbook: globdef.config.add_previous_file(self.filename)
else: ok_song = 1
if save_songbook and self.songbook:
ok_songbook = 0
if not self.songbook.filename: self.on_save_as_songbook()
if self.songbook.filename:
#self.songbook.__xml__(codecs.lookup("utf8")[3](open(self.songbook.filename, "w")))
s = StringIO()
self.songbook.__xml__(s)
s = s.getvalue()
open(self.songbook.filename, "w").write(s)
ok_songbook = 1
globdef.config.add_previous_file(self.songbook.filename)
else: ok_songbook = 1
if ok_song and ok_songbook: self.last_undoables = self.undo_stack.undoables[:]
def on_preview_print(self, event = None):
if not self.song: return
try:
songwrite3.plugins.get_exporter("pdf").print_preview(self.song)
except:
self._treat_export_error()
def on_preview_print_songbook(self, event = None):
if not self.songbook: return
try:
songwrite3.plugins.get_exporter("pdf").print_preview(self.songbook)
except:
self._treat_export_error()
def _treat_export_error(self):
error_class, error, trace = sys.exc_info()
sys.excepthook(error_class, error, trace)
print(file = sys.stderr)
if isinstance(error, model.SongwriteError):
editobj3.edit(error)
if isinstance(error, model.TimeError):
self.canvas.deselect_all()
if error.note:
self.canvas.partition_2_drawer[error.note.partition].select_note(error.note)
elif error.partition and error.time:
self.canvas.partition_2_drawer[error.partition].select_at(error.time, 0, 0)
elif error.time:
self.canvas.partition_2_drawer[self.song.partitions[0]].select_at(error.time, 0, 0)
else:
editobj3.edit(_(error.__class__.__name__) + "\n\n" + str(error))
def on_about(self, *args):
class About(object):
def __init__(self):
self.details = _("__about__")
self.icon_filename = os.path.join(globdef.DATADIR, "songwrite_about.png")
self.url = "http://www.lesfleursdunormal.fr/static/informatique/songwrite/index_en.html"
self.licence = "GPL"
self.authors = "Jean-Baptiste 'Jiba' Lamy "
def __str__(self): return "Songwrite 3 version %s" % model.VERSION
descr = introsp.description(About)
descr.def_attr("details" , field.HiddenField)
descr.def_attr("icon_filename", field.HiddenField)
self.edit(About())
def on_manual(self, *args):
DOCDIR = os.path.join(globdef.APPDIR, "doc")
if not os.path.exists(DOCDIR):
import glob
DOCDIR = glob.glob(os.path.join(globdef.APPDIR, "..", "doc", "songwrite3*"))[0]
if not os.path.exists(DOCDIR):
DOCDIR = glob.glob("/usr/share/doc/songwrite3*")[0]
if not os.path.exists(DOCDIR):
DOCDIR = glob.glob("/usr/share/local/doc/songwrite3*")[0]
if not os.path.exists(DOCDIR):
DOCDIR = glob.glob("/usr/doc/songwrite3")[0]
DOC = os.path.join(DOCDIR, locale.getdefaultlocale()[0][:2], "doc.pdf")
if not os.path.exists(DOC):
DOC = os.path.join(DOCDIR, "en", "doc.pdf")
DOC = os.path.abspath(DOC)
pdf_command = globdef.config.get_preview_command_pdf()
if "%s" in pdf_command:
#os.system(pdf_command % DOC)
p = subprocess.Popen(pdf_command % DOC, shell = True, close_fds = True)
else:
if pdf_command.endswith("-"): pdf_command = pdf_command[-1]
#os.system(pdf_command + " " + DOC)
p = subprocess.Popen(pdf_command + " " + DOC, shell = True, close_fds = True)
def on_add_instrument(self, event = None):
if self.selected_partition:
if isinstance(self.selected_partition, introsp.ObjectPack): selected_partition = self.selected_partition.objects[-1]
else: selected_partition = self.selected_partition
else: selected_partition = None
def callback(*args): pass
song = self.song or new_song()
descr = introsp.description(model.Song)
if selected_partition:
action = introsp.InsertAction(descr.attributes["partitions"])
new_child = descr.do_action(action, self.undo_stack, None, song, descr.attributes["partitions"], selected_partition, callback)
else:
action = introsp.AppendAction(descr.attributes["partitions"])
new_child = descr.do_action(action, self.undo_stack, None, song, callback)
if not self.song is song: self.set_song(song)
def on_dupplicate_instrument(self, event = None):
if not self.song: return
if isinstance(self.selected_partition, introsp.ObjectPack):
partitions = self.selected_partition.objects
else: partitions = [self.selected_partition]
insert_at = max([self.song.partitions.index(partition) for partition in partitions])
xml = StringIO()
xml.write("\n")
context = model._XMLContext()
for partition in partitions: partition.__xml__(xml, context)
xml.write("")
import songwrite3.stemml as stemml
xml.seek(0)
song = stemml.parse(xml)
def do_it(song = song):
for partition in song.partitions:
self.song.insert_partition(insert_at, partition)
partition.song = self.song
def undo_it(song = song):
for partition in song.partitions:
self.song.remove_partition(partition)
undoredo.UndoableOperation(do_it, undo_it, _("Duplicate instrument"), self.undo_stack)
def on_remove_instrument(self, event = None):
if self.song and self.selected_partition:
descr = introsp.description(self.song.__class__)
descr.do_action(introsp._REMOVE_ACTION, self.undo_stack, None, self.song, descr.attributes["partitions"], self.selected_partition)
self.set_selected_partition(None)
def on_move_instrument_up(self, event = None):
if self.song and self.selected_partition:
descr = introsp.description(self.song.__class__)
descr.do_action(introsp._MOVE_UP_ACTION, self.undo_stack, None, self.song, descr.attributes["partitions"], self.selected_partition)
def on_move_instrument_down(self, event = None):
if self.song and self.selected_partition:
descr = introsp.description(self.song.__class__)
descr.do_action(introsp._MOVE_DOWN_ACTION, self.undo_stack, None, self.song, descr.attributes["partitions"], self.selected_partition)
def on_song_prop(self, event = None):
if self.song: self.edit(self.song)
def on_instrument_prop(self, event = None):
if self.song and self.selected_partition:
self.partition_editor = self.edit(self.selected_partition)
self.partition_editor.window.resize(self.partition_editor.window.width(), app.desktop().screenGeometry(self.window).height() * 0.8)
def on_note_prop(self, event = None):
if self.selected_note: self.note_editor = self.edit(self.selected_note)
def on_songbook_prop(self, event = None):
if not self.songbook:
self.on_new_songbook()
return
def on_edit_child(song_ref):
if isinstance(song_ref, model.SongRef):
self.set_song(song_ref.get_song())
self.edit(self.songbook, on_edit_child = on_edit_child)
def on_bars_prop(self, event = None):
mesures = self.canvas.get_selected_mesures()
if mesures:
if len(mesures) == 1: mesures = mesures[0]
else: mesures = introsp.ObjectPack(mesures)
self.mesure_editor = self.edit(mesures)
def on_playlist_prop(self, event = None):
if self.song:
editor = self.edit(self.song.playlist)
editor.window.resize(650, 400)
def on_preferences(self, *args):
globdef.config.edit()
def on_play(self, *args):
if self.canvas:
player.play(songwrite3.midi.song_2_midi(self.song, trackable = 1), 0, self.canvas.play_tracker)
def on_play_in_loop(self, *args):
if self.canvas:
player.play(songwrite3.midi.song_2_midi(self.song, trackable = 1), 1, self.canvas.play_tracker)
def on_play_from_here(self, *args):
if self.canvas:
if player.is_playing(): player.stop()
else: player.play(songwrite3.midi.song_2_midi(self.song, self.canvas.get_selected_time(), trackable = 1), 0, self.canvas.play_tracker)
def on_play_from_here_in_loop(self, *args):
if self.canvas:
player.play(songwrite3.midi.song_2_midi(self.song, self.canvas.get_selected_time(), trackable = 1), 1, self.canvas.play_tracker)
def on_stop_playing(self, *args):
player.stop()
def on_space(self, *args): # space : play from the current position
if player.is_playing(): player.stop()
else: self.on_play_from_here()
def on_select_all(self, *args):
if self.canvas: self.canvas.select_all()
def on_note_cut(self, *args):
if self.canvas: self.canvas.on_cut()
def on_note_copy(self, *args):
if self.canvas: self.canvas.on_copy()
def on_note_paste(self, *args):
if self.canvas: self.canvas.on_paste()
def on_insert_times(self, *args):
if not self.canvas: return
class InsertTimesOptions(object):
def __init__(self):
self.nb_time = 1
self.details = _("""Use negative values for deleting beats.""")
self.icon_filename = os.path.join(globdef.DATADIR, "song.png")
def __str__(self): return _("Insert/remove beats...")
o = InsertTimesOptions()
def on_validate(o):
if (not o) or (not o.nb_time): return
at = self.canvas.get_selected_time()
delta = o.nb_time * 96
removed = []
def shift(delta):
old_removed = removed[:]
del removed[:]
for partition in self.song.partitions:
if isinstance(partition, model.Partition):
i = 0
while i < len(partition.notes):
note = partition.notes[i]
if note.time >= at:
if (delta < 0) and note.time < at - delta:
partition.remove(partition.notes[i])
removed.append((partition, note))
continue
else: note.time += delta
i += 1
for partition, note in old_removed: partition.add_note(note)
self.song.rythm_changed()
self.canvas.render_all()
self.canvas.update_time()
def do_it (): shift( delta)
def undo_it(): shift(-delta)
if o.nb_time > 0: name = _("Insert %s beat(s)") % o.nb_time
else: name = _("Delete %s beat(s)") % -o.nb_time
undoredo.UndoableOperation(do_it, undo_it, name, self.undo_stack)
editobj3.edit(o, on_validate = on_validate, menubar = False)
def on_insert_bars(self, *args):
if not self.canvas: return
class InsertBarsOptions(object):
def __init__(self):
self.nb_bar = 1
self.details = _("""Use negative values for deleting bars.""")
self.icon_filename = os.path.join(globdef.DATADIR, "song.png")
def __str__(self): return _("Insert/remove bars...")
o = InsertBarsOptions()
def on_validate(o):
if (not o) or (not o.nb_bar): return
mesures = self.canvas.get_selected_mesures()
if mesures: mesure = mesures[0]
else: mesure = self.song.mesures[0]
mesure_pos = self.song.mesures.index(mesure)
at = mesure.time
removed = []
playlist_items_values = []
def shift(nb_bar):
# Add / remove mesures
time = self.song.mesures[mesure_pos].time
if nb_bar > 0:
for i in range(mesure_pos, mesure_pos + nb_bar):
self.song.mesures.insert(i, model.Mesure(self.song, time, mesure.tempo, mesure.rythm1, mesure.rythm2, mesure.syncope))
time += mesure.duration
else:
del self.song.mesures[mesure_pos : mesure_pos - nb_bar]
i = mesure_pos
# Shift playlist
if playlist_items_values:
for item, from_mesure, to_mesure in playlist_items_values:
item.from_mesure = from_mesure
item.to_mesure = to_mesure
del playlist_items_values[:]
else:
for item in self.song.playlist.playlist_items:
playlist_items_values.append((item, item.from_mesure, item.to_mesure))
if item.from_mesure >= mesure_pos:
if item.from_mesure < mesure_pos - nb_bar: item.from_mesure = mesure_pos
else: item.from_mesure += nb_bar
if item.to_mesure >= mesure_pos - 1:
if item.to_mesure < mesure_pos-1-nb_bar: item.to_mesure = mesure_pos - 1
else: item.to_mesure += nb_bar
# Shift notes
old_removed = removed[:]
del removed[:]
delta = nb_bar * mesure.duration
for partition in self.song.partitions:
if isinstance(partition, model.Partition):
i = 0
while i < len(partition.notes):
note = partition.notes[i]
if note.time >= at:
if (delta < 0) and note.time < at - delta:
partition.remove(partition.notes[i])
removed.append((partition, note))
continue
else: note.time += delta
i += 1
for partition, note in old_removed: partition.add_note(note)
self.song.rythm_changed() # AFTER moving the notes !!!
self.canvas.render_all()
self.canvas.update_time()
def do_it (): shift( o.nb_bar)
def undo_it(): shift(-o.nb_bar)
if o.nb_bar > 0: name = _("Insert %s bar(s)") % o.nb_bar
else: name = _("Delete %s bar(s)") % -o.nb_bar
undoredo.UndoableOperation(do_it, undo_it, name, self.undo_stack)
editobj3.edit(o, on_validate = on_validate, menubar = False)
def on_note_arrange(self, *args):
if not self.canvas: return
class ArrangeAtFretOptions(object):
def __init__(self):
self.fret = 0
self.details = _("Arrange selected notes at fret?")
self.icon_filename = os.path.join(globdef.DATADIR, "song.png")
def __str__(self): return _("Arrange notes at fret...")
o = ArrangeAtFretOptions()
def on_validate(o):
if o: self.canvas.arrange_selection_at_fret(o.fret)
editobj3.edit(o, on_validate = on_validate, menubar = False)
def on_dump(self, *args):
if not self.song: return
print(self.song.__xml__().getvalue(), file = sys.stderr)
def on_gc(self, *args):
import gc
print(gc.collect(), "object(s) collected", file = sys.stderr)
for o in gc.garbage: print("Garbage:", o, file = sys.stderr)
print(file = sys.stderr)
#import memtrack, weakref
#ref = weakref.ref(self.songbook.song_refs[0].get_song())
#if ref(): memtrack.reverse_track(ref())
scan()
def set_duration_longer (self): self.set_duration_longer_or_shorter(True)
def set_duration_shorter(self): self.set_duration_longer_or_shorter(False)
def set_duration_longer_or_shorter(self, longer):
if not self.canvas: return
if longer: notes = [note for note in self.canvas.selections if note.base_duration() < 384]
else: notes = [note for note in self.canvas.selections if note.base_duration() > 12 ]
def decrease_duration(notes = notes):
for note in notes: self.canvas.current_duration = note.duration = note.duration // 2
def increase_duration(notes = notes):
for note in notes: self.canvas.current_duration = note.duration = note.duration * 2
if longer: undoredo.UndoableOperation(increase_duration, decrease_duration, _("increase note duration"), self.undo_stack)
else: undoredo.UndoableOperation(decrease_duration, increase_duration, _("decrease note duration"), self.undo_stack)
def set_note_menus_enabled(self, enabled):
# Needed because Qt considers AltGr + 8 (= backslash \) as a "_" for menu accels
for menu in self.fx_menus.values(): self.set_menu_enable(menu, enabled)
def on_pipe_command_received(self, info_message):
print("EMITTED", info_message)
class InstrumentChooserPane(qtwidgets.QWidget):
def __init__(self, main):
qtwidgets.QWidget.__init__(self)
layout = qtwidgets.QVBoxLayout()
layout.addWidget(qtwidgets.QLabel(), 3)
label = qtwidgets.QLabel("%s\n " % _("Choose an instrument:"))
layout.addWidget(label, 0, qtcore.Qt.AlignHCenter)
image = qtwidgets.QLabel()
pixmap = qtgui.QPixmap(os.path.join(globdef.DATADIR, "instruments.png"), "png")
image.setPixmap(pixmap)
image.mouseReleaseEvent = self.on_mouse_release
layout.addWidget(image, 0, qtcore.Qt.AlignHCenter)
layout.addWidget(qtwidgets.QLabel(), 2)
self.setLayout(layout)
self.main = main
self.image = image
self.pixmap_width = pixmap.width()
def on_mouse_release(self, event):
x = event.x()
if not(0 <= x < self.pixmap_width): return
s = new_song()
p = model.Partition(s)
s.partitions.append(p)
if x < 100:
p.view = model.GuitarView(p)
sound_file = "son_guitare.mid"
elif x < 168:
from songwrite3.plugins.lyre import SevenStringLyreView
p.view = SevenStringLyreView(p)
sound_file = "son_lyre.mid"
elif x < 255:
p.view = model.VocalsView(p)
sound_file = "son_chant.mid"
elif x < 362:
from songwrite3.plugins.accordion import AccordionView
from songwrite3.plugins.chord import AccordionChordView
p.view = AccordionView(p)
p2 = model.Partition(s)
s.partitions.append(p2)
p2.view = AccordionChordView(p2)
p2.volume = 128
sound_file = "son_accordeon.mid"
else:
from songwrite3.plugins.fingering import TinWhistleView
p.view = TinWhistleView(p)
sound_file = "son_flute.mid"
player.play(open(os.path.join(globdef.DATADIR, sound_file), "rb").read())
for p in s.partitions: p.instrument = p.view.default_instrument
self.main.filename = None
self.main.set_song(s)
def new_song():
s = model.Song()
s.authors = getpass.getuser().title()
s.title = _("%s's song") % s.authors
s.copyright = "Copyright %s by %s" % (time.localtime(time.time())[0], s.authors)
return s
Songwrite3-0.1/data/ 0000755 0023421 0023421 00000000000 13155312423 014160 5 ustar jiba jiba 0000000 0000000 Songwrite3-0.1/data/lyre.png 0000644 0023421 0023421 00000012133 12702520305 015636 0 ustar jiba jiba 0000000 0000000 PNG
IHDR d d pT sRGB bKGD pHYs tIME
qXٵ IDATx{t}?3~IYpld&N(v4 %%nRH4
4$iOCM!MOeI(I!I- JZiWc'?%<.z-yԬ ~ @8sE 8">-XkƙnZunv ȪUH*.H" %Use:]\lU$7T[E )}aEa<" ŕi?VÄ"˚ܲKܬ)q옑?qƆ9;m?oY^)lE Dc-6|ut oLܡ Ó?hm`gH_76uӽr,7=bI%#5+k?l46Є#?}CVx 37Zb<ys`r 8DvnY]xg߰K[cƱޑgK~$[ޑFkOCZi) De
XI܅ŻiU}GbOغVcP\Y|sI)HH¿}[V;$Qܗ{g\sec$,[`b-r Qۇ[w?F;O2`LhJ||}[Y06/#p+ ]-܍loN7Z;Tyf`H*tU,e{%eb
k#( FN
??P艧{o4(
^+[WO۲B4/ KŞ\+hD*r\[w/@a?ߺ{)&ܵo 3gvw_ZԱd?WHwzش㷪;dU8,l.'DܰC ?{ahR!T$T)q7DLqXn3eԺ-c