From f5bbedc6bf28384274251625181e3ff7f8824182 Mon Sep 17 00:00:00 2001 From: Adam Pribyl Date: Mar 19 2018 17:39:52 +0000 Subject: Initial commit of original package from http://members.chello.nl/w.boeke/amuc/ --- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7f8c64c --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +# For Amuc - the A'dam Music Composer + +AMUC_DIR=src +A2PS_DIR=src-abcm2ps +W2S_DIR=src-wav2score +TRSCO_DIR=src-tr-sco +SDIR=/usr/share +INSTALL_DIR=$(SDIR)/amuc +BIN_DIR=/usr/bin +DOC_DIR=$(SDIR)/doc/amuc +# sometimes 'sudo make install' does not evaluate $(PWD) correctly, thus: +PWD=$(shell pwd) + +.SUFFIXES= +.PHONY: all a2ps_lib wav2score tr-sco install links uninstall + +all: a2ps_lib amuc wav2score tr-sco + +a2ps_lib: + make -C $(A2PS_DIR) + +amuc: + make -C $(AMUC_DIR) + +wav2score: + make -C $(W2S_DIR) + +tr-sco: + make -C $(TRSCO_DIR) + +install: + if test -e /usr/local/bin/amuc; then make uninstall; fi + if test -L $(BIN_DIR)/amuc; then make uninstall; fi + if test ! -d $(INSTALL_DIR); then mkdir $(INSTALL_DIR); fi + if test ! -d $(DOC_DIR); then mkdir $(DOC_DIR); fi + cp $(AMUC_DIR)/amuc $(BIN_DIR)/amuc + cp $(A2PS_DIR)/abcm2ps $(BIN_DIR)/abcm2ps + cp $(W2S_DIR)/wav2score $(BIN_DIR)/wav2score + cp $(TRSCO_DIR)/tr-sco $(BIN_DIR)/tr-sco + strip $(BIN_DIR)/amuc $(BIN_DIR)/wav2score $(BIN_DIR)/abcm2ps + rm -rf $(INSTALL_DIR)/samples; cp -r samples $(INSTALL_DIR) + cp tunes/monosynth-patches $(INSTALL_DIR) + cp tunes/chords-and-scales $(INSTALL_DIR) + cp doc/amuc.1 $(SDIR)/man/man1 + cp doc/* $(DOC_DIR) + +# create links for executables +links: + if test ! -d $(INSTALL_DIR); then mkdir $(INSTALL_DIR); fi + ln -sf $(PWD)/$(AMUC_DIR)/amuc $(BIN_DIR)/amuc + ln -sf $(PWD)/$(A2PS_DIR)/abcm2ps $(BIN_DIR)/abcm2ps + ln -sf $(PWD)/$(W2S_DIR)/wav2score $(BIN_DIR)/wav2score + ln -sf $(PWD)/$(TRSCO_DIR)/wav2score $(BIN_DIR)/tr-sco + +uninstall: + rm -f $(BIN_DIR)/amuc $(BIN_DIR)/abcm2ps $(BIN_DIR)/wav2score + rm -rf $(INSTALL_DIR) + rm -f $(SDIR)/man/man1/amuc.1 + rm -rf $(DOC_DIR) diff --git a/README.md b/README.md index 7c57e35..147c4c2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,162 @@ -# amuc -The Amsterodam Music Composer +# This is Amuc - the Amsterdam Music Composer, version 1.7 +Last updated: jan 10, 2009 + +Latest version of this software is available from Amuc's home page: + http://members.chello.nl/w.boeke/amuc/index.html +It is also at sourceforge: + https://sourceforge.net/projects/amuc +however there it is updated less frequently. + +Compiling +========= +Should pose no problems on any modern Linux system. First run: + ./configure + +This very basic configure script only tests whether the library and include +files for x11, alsa, xft, cairo and jack are present on your system. + +Then issue: + make + +Four programs will be created: +in directory src: amuc, +in src-abcm2ps: abcm2ps (to create human-readable scores in postscript), +in src-wav2score: wav2score (to create score files from audio), +in src-tr-sco: tr-sco (to modify score files). + +Located in the src directory is a tune called 'dance'. After successful +compilation you can try it: + ./amuc dance.scr + +To hear the tune, click the 'play' button at the right. + +If you start the app with option -h, you'll get a short usage message. + +Install +======= +To install Amuc, type (as root): + make install +This will copy: + To /usr/bin: + amuc, + abcm2ps, + wav2score, + tr-sco. + To /usr/share/amuc: + monosynth-patches (patches for the mono synthesizers), + chords-and-scales (for the 'chords' window). + To /usr/share/amuc/samples: + wave files (for the sampled instruments), + To /usr/share/amuc/doc: + amuc-man.html + pictures + To /usr/share/man/man1: + amuc.1 + +The executables also can be installed using symlinks. In this case issue +command: + make links + +If the program is started for the first time, then a configuration +file .amucrc will be created in your home directory. You can modify it, +it will be read when you start Amuc the next time. + +No C++ compiler? +================ +Without a compiler and development libraries you won't come very far. +For Ubuntu you can install them by: + sudo aptitude install \ + build-essential libasound2-dev libx11-dev libfreetype6-dev libxft-dev + +Demo's +====== +In the tunes directory there are some more demo's: + + rising.scr - featuring sampled percussion + how-low.scr - featuring builtin instruments + how-synth.scr - featuring mono synthesizers + org-concerto.scr - first part of Haendel's organ concerto opus 4, nr 4 + +Creating a Debian package +========================= +Read the README file in directory 'debian'. + +Source files +============ +The following C++ files form the main program, amuc. + src/amuc.cpp - the main program + src/str.cpp - general string handling + src/x-widgets.cpp - the gui components + src/sound.cpp - everything to create sound + src/dump-wav.cpp - WAVE file creation + src/midi-out.cpp - MIDI file creation + src/read-wav.cpp - read wave files for sampled instruments + src/snd-interface.cpp - interface to alsa sound drivers and jack deamon + src/midi-keyb.cpp - interface for MIDI keyboard, read from /dev/midi + src/midi-in.cpp - to read MIDI files + src/chords.cpp - the chords window +The following files are GPL'ed: + physical-mod.cpp - physical models for sampled instruments + src-abcm2ps/*.cpp - files for lib abc2ps.a and program abcm2ps + mono-synth.cpp - mono synthesizer + src-wav2score/*.cpp - create score from wave file + +Documentation +============= +The doc directory contains a manual, file amuc-man.html. +This can also be accessed via the 'Help' menu. + +Postscript output +================= +Beautiful traditional scores can be printed from generated postscript +files. Also output in ABC format is provided, which can be edited if +needed, and translated to postscript by abcm2ps (in directory +src-abcm2ps). Abcm2ps is a C++ port from a clever program +(abcm2ps, version abcm2ps-3.7.21) made by Michael Methfessel. +Three text files provide information about features, keywords etc. +General information about ABC notation can be found at: + http://staffweb.cms.gre.ac.uk/~c.walshaw/abc/ + +Default settings for abcm2ps are built-in, see file: + src-abcm2ps/format.cpp. + +Connecting a USB MIDI keyboard +============================== +Modern Linux distribution have built-in support for many MIDI keyboards. +They can be accessed e.g. via /dev/midi1. To check this, look if such a +file exists after you switched on a MIDI keyboard. To check whether +MIDI messages are transferred, try + cat /dev/midi1 +Then output should appear on the screen when you play some notes. + +Also a connection via Jack is possible. This choice can be made in the +~/.amucrc configuration file. + +Modification history +==================== +NB! This app is updated now and then (to add a feature or to remove a bug), +but doesn't get a new version number each time. + +Version 1.7 - last update: febr 2009 + - Wav2score: options modified. + - Code has been adapted to the newest gcc compiler (version 4.3.2) which is + very strict regarding type checking. + - Mono-synth: modified controls, modified patch-control string (but older patches + stil work). + - Support for Jack. + - 'libusb' connection with Midi keyboard omitted, connection via Jack provided. + - Anti-aliased graphics. + - Options -trh etc. handled by new app: tr-sco. + +Version 1.6 - last update: july 2008 +Version 1.5 - last update: nov 2007 +Version 1.4 - july 2007 +Version 1.3 - dec 2006 +Version 1.2 - jan 2006 +Version 1.1 - sept 2005 +Version 1.0 - april 2005 + +---------------------------------------------------------------------------- +Good luck. + +Wouter Boeke +w.boeke@chello.nl diff --git a/amuc-icon.png b/amuc-icon.png new file mode 100644 index 0000000..18fd1ea Binary files /dev/null and b/amuc-icon.png differ diff --git a/amuc.desktop b/amuc.desktop new file mode 100644 index 0000000..66fc2fc --- /dev/null +++ b/amuc.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=Amuc +GenericName=the Amsterdam Music Composer +Comment=A program for music composition +Icon=amuc-icon +#Icon=/usr/share/pixmaps/amuc-icon.png +Exec=amuc +Terminal=false +MimeType=text/plain; +Categories=AudioVideo; diff --git a/configure b/configure new file mode 100755 index 0000000..d68e38e --- /dev/null +++ b/configure @@ -0,0 +1,50 @@ +CC=g++ +MAKE_INC=Makefile.inc + +rm -f $MAKE_INC + +CFLAGS="" +LDFLAGS="" +okay=yes +for LIB in x11 alsa xft cairo jack +do + message=yes + echo -n "library $LIB ... " + if `pkg-config --exists $LIB` + then + CFLAGS=$CFLAGS\ `pkg-config --cflags $LIB` + LDFLAGS=$LDFLAGS\ `pkg-config --libs $LIB` + else + okay=no + message=no + fi + echo $message +done +if [ $okay = no ]; then echo "Some libraries are missing, exit."; exit; fi + +# compiler test +echo -n "alsa version ... " +cat > conftest.cpp < +#include +int main() { + if (SND_LIB_MAJOR<1) + printf("%d.%d (should be 1.0 or higher)",SND_LIB_MAJOR,SND_LIB_MINOR); + else + puts("okay"); +} +!! +rm -f a.out +$CC conftest.cpp -lasound +okay=`./a.out` +echo "$okay" +rm -f a.out conftest.cpp +if [ "$okay" != okay ]; then echo "Please upgrade alsa."; exit; fi + +echo "CFLAGS=$CFLAGS" > $MAKE_INC +echo "LDFLAGS=$LDFLAGS" >> $MAKE_INC +jack_min_version=`pkg-config --modversion jack | sed 's/0.\([0-9]*\).\([0-9]*\)/\1/'` +echo "JACK_MIN_VERSION=$jack_min_version" >> $MAKE_INC + +echo "File $MAKE_INC created." +echo "Now run 'make', then 'make install' or 'make links'." diff --git a/debian/amuc-menu b/debian/amuc-menu new file mode 100644 index 0000000..9887a16 --- /dev/null +++ b/debian/amuc-menu @@ -0,0 +1,4 @@ +?package(amuc): needs="X11" section="Applications/Sound" \ + title="Amuc" hints="Audio" \ + longtitle="Amuc - the Amsterdam Music Composer" \ + command="/usr/bin/amuc" icon="/usr/share/pixmaps/amuc-icon.png" diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..2ec36bb --- /dev/null +++ b/debian/control @@ -0,0 +1,23 @@ +Package: amuc +Version: 1.7-1 +Section: sound +Priority: optional +Architecture: i386 +Depends: libgcc1, libc6, libasound2, libcairo2, libjack0 +Maintainer: W Boeke +Description: The Amsterdam Music Composer + Amuc is a Linux application for composing and playing music. + It works as follows: + . + Tune fragments are entered graphically. These are combined into a complete + tune by means of a script file. + Tunes can be exported as WAVE or MIDI files, or as human-readable scores + in postscript format. + MIDI files can be imported. + WAVE files can be imported and transformed to notes. + Samples (e.g. for percussion instruments) can be read from WAVE files. + Support for USB keyboard. + Five-bar staff or piano roll display. + For the inexperienced composer there is help available with scales and chords. + . +Homepage: http://members.chello.nl/w.boeke/amuc/index.html diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..ac246ee --- /dev/null +++ b/debian/copyright @@ -0,0 +1 @@ +Debian maintainer: W.Boeke \ No newline at end of file diff --git a/debian/deb-create b/debian/deb-create new file mode 100755 index 0000000..e93db27 --- /dev/null +++ b/debian/deb-create @@ -0,0 +1,47 @@ +set -e + +ADIR=$PWD/.. +BDIR=debian/usr/bin +SDIR=debian/usr/share +IDIR=$SDIR/amuc +DDIR=$SDIR/doc/amuc + +if [ -d debian ]; then rm -r debian; fi +mkdir debian debian/DEBIAN debian/usr $BDIR $SDIR $IDIR $SDIR/pixmaps \ + $SDIR/menu $SDIR/lintian $SDIR/lintian/overrides $SDIR/applications \ + $SDIR/man $SDIR/man/man1 $IDIR/amuc-tunes $IDIR/samples $SDIR/doc $DDIR + +cp $ADIR/src/amuc $ADIR/src-abcm2ps/abcm2ps $ADIR/src-wav2score/wav2score $ADIR/src-tr-sco/tr-sco $BDIR +strip --remove-section=.comment --remove-section=.note $BDIR/* +cp ../amuc.desktop $SDIR/applications +cp control postinst postrm debian/DEBIAN +cp copyright $DDIR + +cp changelog tmp +echo "" >> tmp +echo " -- W.Boeke `date +%F`" >> tmp +cp tmp $DDIR/changelog.Debian + +cp ../amuc-icon.png ../amuc-icon.xpm $SDIR/pixmaps +cp amuc-menu $SDIR/menu/amuc + +echo 'amuc: menu-icon-not-in-xpm-format + amuc: description-contains-homepage' > $SDIR/lintian/overrides/amuc + +cp $ADIR/samples/*.wav $IDIR/samples +cp $ADIR/doc/* $SDIR/doc/amuc +cp $ADIR/tunes/monosynth-patches $IDIR +cp $ADIR/tunes/chords-and-scales $IDIR +cp $ADIR/doc/amuc.1 $SDIR/man/man1/amuc.1 +cp $ADIR/src/dance.sc? $ADIR/tunes/rising.sc? $ADIR/tunes/how-low.sc? $ADIR/tunes/how-synth.sc? $IDIR/amuc-tunes +cp $ADIR/tunes/org-concerto.sc? $ADIR/tunes/org-concerto.hd $IDIR/amuc-tunes + +gzip -9 -f $SDIR/man/man1/amuc.1 $SDIR/doc/amuc/changelog.Debian +dpkg-deb --build debian +if [ -z $USERNAME ] +then + lintian --show-overrides debian.deb +else + su $USERNAME -c 'lintian --show-overrides debian.deb' +fi +mv debian.deb amuc_1.7-1_i386.deb diff --git a/debian/postinst b/debian/postinst new file mode 100755 index 0000000..c9efdb7 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,5 @@ +#!/bin/sh +if [ $1 = "configure" ] && [ ! -z `which update-menus` ] +then + update-menus +fi diff --git a/debian/postrm b/debian/postrm new file mode 100755 index 0000000..a30dbac --- /dev/null +++ b/debian/postrm @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +# Automatically added by dh_installmenu +if [ -x "`which update-menus 2>/dev/null`" ]; then update-menus ; fi +# End automatically added section diff --git a/doc/a-title.png b/doc/a-title.png new file mode 100644 index 0000000..106e53d Binary files /dev/null and b/doc/a-title.png differ diff --git a/doc/amuc-man.html b/doc/amuc-man.html new file mode 100644 index 0000000..a1605b5 --- /dev/null +++ b/doc/amuc-man.html @@ -0,0 +1,2141 @@ + + + +Amuc, the Amsterdam Music Composer + + + +
+ +
+Version 1.7
+Wouter Boeke
+ +
+Last updated: dec 1, 2008 +

Contents

+ + +

Introduction

+Amuc is a program for composing music and playing it. +From the command line the following command +can be given, where the parameter is the name of a score file: +
   amuc dance.sco 
+(This file is located in the src directory)
+NB! If Amuc was installed by a package manager, with no source code, +then all demo files are available in directory +/usr/share/amuc/amuc-tunes. +If you don't know how to use a terminal, then read Scared of the command line? +

+A score file contains +short tunes that are to be combined into a complete piece of music. +This combining is controlled by a script file which is a normal text file. +After the script file dance.scr has been read the screen may look as follows. +

+

+

+After clicking the play button at the right, you can listen +to this cheerful song. +

+It is also possible to read the script file directly, when no score file +is loaded yet. If script +file dance.scr is read, then first dance.sco will +be loaded if it exists. The script file can be created or modified by the +built-in editor, or by an external editor. +

+If Amuc is started for the first time, then a default configuration file .amucrc +is written to your HOME directory. The items in this file are hopefully self-explaining. +A.o. you can choose where the sound must go: directly to your sound card using Alsa, +or via Jack (the Jack Audio Connection Kit). In the latter case, Amuc tries to connect +to the standard Alsa input ports: playback_1 and playback_2. You could +choose other ports, which is easily accomplished when using a Jack GUI-backend tool (e.g. qjackctl). +

+Amuc is quite different from other music software. It is especially focused on +composing music, which is a very difficult but also rewarding endeavor. +The tool tries to place as little hurdles as possible on the user's road. +In general it's a good idea to run Amuc from the command line, and not by +double-clicking its icon. +For bigger projects it is convenient to first collect all +relevant files in one directory and run Amuc from there. +To create complicated pieces, or to create scores for the individual musicians +of an orchestra, you should know how to use a text editor and how to write shell scripts. +

+The entering of new tunes is done on normal 5-bar staffs (treble and bass clef) in +one of the 2 panels at the left. There are 2 +kinds of instruments: sampled instruments and instruments whose waveforms +are generated real-time. Fore each kind there is choice between 6 instruments, +indicated by a color. All real-time instruments can be replaced by a Moog-like +monophonic synthesizer. +The sound of each instrument can be modified via +its own control panel, that will appear when the appropriate color is selected. +

+The sampled instruments are created with a technique called +"physical modeling", or alternativily their waveforms are read from files in WAVE format. In this +case each sampled instrument can be tied to each of the available wave files. +The pitch of sampled instruments can be controlled by the vertical position of their notes +if a checkbox controlled pitch is enabled. +

+The characteristics of a real-time instrument are controlled by algorithms. So if you know how +to program, then the creation of different +sounds belongs to the possibilities. How Amuc is controlled will be explained +in the next chapter via the panels and widgets of the graphical user interface. +

+Amuc can be run with several options. Run 'amuc -h' +to get a complete list, or run 'man amuc'. + + +

The GUI panels

+ +

The 2 score panels

+Here you see a 5-bar staff in treble clef (the black lines) and in bass clef +(the green lines). Note units can be entered by clicking the left mouse button. +If a note is already present, then it will be erased, unless key N is pressed. +In this case multiple notes can be inserted at the same location, they will +be rendered in a grey color. +If key N is not used, then long notes can be entered or erased by dragging the mouse. +The color of the notes is determined by the color control panel (middle of the window). +

+If the middle mouse button is used, +then the note will be staccato (indicated by a shortened display of the note). +If the note is staccato already it will be turned back to normal. +

+Middle-C, which is a note on the line between the black and the green score lines, +will have a frequency of 130.8Hz (which is half of the usual value 261.8Hz). +

+Notes from sampled instruments are created if the sampled notes checkbox is +enabled. The control panel for all sampled instruments is the same: 2 two-dimensional +sliders controlling values called speed, tension, decay and amplitude. +The default values for these instruments are different however. Sampled notes +are indicated by colored crosses. Their vertical position in the score panel is relevant +only if checkbox controlled pitch is enabled. +

+The control panel for a sampled instrument contains a choice box. If the wave file +item is choosen, then the waveforms for the sampled sounds are read from +files. A menu widget is provided, if clicked then the available sampled +instruments will be listed. The names of wave files in the current directory are +rendered in black, the files in /usr/share/amuc/samples in blue. +For details see chapter Sampled Instruments. +Notice that these +files are read if the score at the bottom +is played and they are needed for the first time. So if only tunes with +real-time instruments are played then the wave files will not be loaded. +

+Clicking the right mouse button will create a vertical 'end-of-tune' +line. This will be useful when several tunes are combined later on, because then +no timing information will have to be specified in the script file. +Also, this way the length of a tune can be increased beyond its initial value. +To accomplish this, click with the right mouse button in the white space +at the right from the score lines. +

+One score panel can be the active score, which is +controlled by the active button. +If this button is clicked once more, then none of the scores will be active. +The status of the active button is relevant for several different +operations, thus it can be harmful if it is enabled inadvertantly. +Therefore it is always reset after being used. If you don't want this, +you can keep the status +by pressing the shift key before clicking a button or slider. +

+The tune in the score is sounded by clicking the play button of the score panel. +It can be stopped by the stop at the right of the window. +It is possible to indicate the starting and stopping times of the +playback. This is done by clicking the left and right mouse buttons +respectivily, above the highest score line, and is indicated by small red triangles. +They can be undone by clicking at the same point a 2nd time, or by clicking the +middle mouse button. +

+After enabling checkbox zoom a zoomed-out view of the tune +will appear. In this window small note delays can be introduced. See the +zoom window chapter. +

+If the repeat checkbox is enabled, the tune until the 'end-of-tune' line +will be repeated. This also works between the red triangle start/stop signs. The repeating +can be stopped with the stop button. +

+If the checkbox play 1 color is enabled, then a single instrument will sound: the one +that has been choosen in the panel labeled colors (middle right). +

+With the choice button called display the appearance of +the display can be modified. Normally the mode instrument +is enabled. Other display modes: +

    +
  • timing: modified start- or end delays are drawn in +red and blue. Unmodified notes are drawn in grey.
    +
  • accidentals: accidental notes are colored: red for accidental sharp, +blue for accidental flat, black for restored.
    +
  • white keys: no regular scores, but a piano-roll display. +Dotted grey lines indicate the white +keys of a piano keyboard. A score in this mode is too big for its window, so only the upper +part is shown. If the button is clicked a second time, then the lower part appears. +
    +Sometimes it is useful to display the same tune in both score windows, one of them +in white keys mode. +
    +Not only the white piano keys can be shown, also many different scales and chords, which is meant as +an aid for the inexperienced composer. See chapter Scales and score keys. +
+

+Choice button gr# (which means 'group number') is relevant for +MIDI output and for generated human-readable scores (in postscript format). A tune can belong to +1 of 3 'groups'. Each group can be mapped to different MIDI instruments +or to different staves in the postscript picture. See Export MIDI file +or Export postscript file. +

+Usually, musical scores are preceeded by 'flat' or 'sharp' key signatures, depending on the +key in which the piece is written. The key signatures are drawn in front +of the score lines, see chapter Scales and score keys. +If a note is entered on such a line it will be raised or lowered one semitone by default. +

+The color of a new entered note is determined by the colors panel. +

+The action performed by clicking or dragging the left mouse button can be modified +by the buttons at the left side, see the next chapter. + + +

Score edit buttons

+The blue buttons have immediate effect. From the choice buttons, none or one +can be enabled at the same time. A choice button can be reset by clicking it again. +Most buttons have key equivalents (indicated by an underscore in the button label), +which you normally will be using, however using the buttons you can work single-handed. +The key must be pressed before clicking the mouse. +
+
select
+If this button is enabled, then +clicking somewhere on a note will select the parts of the note extending +to the right. Clicking on an already selected part will de-select it. +Selected note parts will get a lighter color. +

+Clicking outside a note will select all note parts under and above the clicked +location. By dragging the mouse an area of selected notes will be created. +

+Selected note parts can be moved, copied, deleted or re-colored. +Notice that the select button stays active after the mouse button has +been released, whereas the other buttens will be reset in this case. +

sel color
+All note parts of one color (as selected in the color panel) under and above the clicked +location will be selected. By dragging the mouse an area of selected notes is created. +

+The operation of other buttons on selected mutiple notes is different if sel color +has been used instead of select. In the first case, e.g. clicking the +delete button would only remove from the multiple note the selected color. In the +second case the complete multiple note would be removed. +

+

sel all ->
+All notes in the clicked score to the right of the cursor will be selected, +except notes to the right of the end-of-score line if it exists. +

+

sel col ->
+Same as sel all ->, but only 1 color (as selected in the color panel) +is selected. Notice that, if a multiple note contains the selected color, +this will be moved to the front. This is relevant because some operations +only affect the front note of a multiple note. +

+

move
+If this button is enabled, then the selected note parts can be moved +in a horizontal or vertical direction, by clicking and dragging the left +mouse button. +

+If notes are moved in vertical direction, then their sign (flat or sharp) +is not retained, it will instead be equal to the +key signature of the new score line. +Notice that if a selected note is moved to a position +beneath or above the valid score area, it will be lost. +

+When clicking in a score different from the score where the selected notes +are, then initially the left-most moved note will appear at the mouse position +in the new score. +

+If the K key is pressed before releasing the mouse button, then +the moving will not occur (K means 'keep'). +

+

copy
+If this button is enabled, then the selected note parts can be copied +in a horizontal or vertical direction, by clicking and dragging the left +mouse button. +The copied notes will be the new selection. +

+When clicking in a score different from the score where the selected notes +are, then initially the left-most copied note will be at the mouse position. +

+Again, if the K key is pressed before releasing the mouse button, then +the copying will not occur. +

+

portando
+Portando means sliding from one note to the next. +Portando is indicated by a slanting line between 2 notes, that must be of the +same color. +If this button is enabled, then: +
    +
  • If the cursor is on a note, the cursor shape becomes a square. Then the +mouse can be dragged, the cursor becomes a cross. If the cursor reaches another +note, the cursor again becomes a square. If the mouse is released at that +moment, then a line is drawn from the first note to the second.
    +
  • If the cursor is on a note which is the start of a portando line, then the +line is omitted.
    +
  • If the cursor is not on a note, then nothing happens. +
+

+Notice that the portando data are stored in the first note, which will be apparent +when this one is moved or deleted. +If 2 notes are in portando, then during playback the decay part of the first +and the attack part of the second are skipped. +The horizontal distance (in note units) between the first and second note can be between 0 and 15, +the vertical distance between -31 and +31. +

+

multi-n
+Enables the insertion of differently colored notes at the same place. +Multi-colored notes are rendered in grey. Notice that +the sign of the new note will be copied from the sign of the first note. +

+

sharp +
flat
+When one of these buttons is enabled then a clicked note +will be sharp or flat respectivily, indicated by small upward or downward peaks on the note. +The up- respectively down arrow keys have the same effect. +

+Notice that normal notes that differ one semitone are treated specially. +For instance a B note that is made sharp will turn into a C note. +

+

normal
+When this button is enabled, notes can be set back from sharp or flat to normal. +Pressing the left arrow key has similar effect. +

+

note info
+When this button is enabled, then clicking on a note has the effect that +information about the note is displayed in an alert window. This also works +for multiple notes. Notes in the score at the bottom will show also the +name of the tune where they came from. +
+Whereas the previous buttons determine what happens when clicking in a score panel, +the following 3 + 4 buttons have immediate effect on the selected notes. +
+
unselect
+The label of button turns to red if any notes have been selected. +After clicking, these will become unselected. You also can +press the U key and then click in the score with selected notes. +

+

re-color
+Clicking this button will have the effect +that selected note parts will get the color as choosen in the color panel. +

+

delete
+After clicking, all selected notes will be gone. +

+

shift
+Selected notes will be shifted up or down by the number of semi-tones, as indicated by +slider shift/cp. +

+

copy
+Selected notes will be copied up or down by the number of semi-tones. +
+ + +

Tunes panel

+This panel shows a list of all tunes in memory. A tune can be displayed +by making one of the scores active before selecting the tune. A new tune +can be added by pressing the new... button and specifying a name via the dialog +panel (middle right). + + +

Meter panel

+Controls the number of note units between the dotted vertical bars in the scores. +

+The meter of some tune also can have a value different from the default meter. To accomplish +this first make one score panel active before modifying the meter slider. +The complete score always has the default meter. + +

Tempo panel

+This controls the duration of the notes. The displayed value is the number +of beats per minute, where 1 beat = 4 note units. + + +

The instrument control panels

+When a color is choosen, and the sampled notes checkbox is +off, then the control panel of the appropriate instrument will +appear. The instruments have not been designed to imitate existing +instruments. They should provide nice and versatile sounds that have +a specific character of their own. +The characteristics of the real-time instruments are as follows. +

+ + + + + + + + + + + + + +
Instrument +
Description
+
black
brown +
An FM instrument, like the Yamaha DX7. The basic sinus signal +can be FM modulated with 0.5 to 8 times the basic frequency. +The modulation index can be choosen between 0 and 10. +Both frequency and index are controlled with a two-dimensional slider. +The modulation index can be either an integer or a value of 0.5 (which yields +a fundamental frequency 1 octave lower). Detuning the modulation +frequency can be done with the detune slider. The modulation index can +itself be modulated with a low-frequency signal, with controllable amount and +frequency. A wide range of sounds is provided, from mellow to pregnant. +
red +The waveform is a repeating pattern consisting of 2, 3 or 5 cycles +(with varying frequency) of a sinus wave. +This yields a frequency spectrum with a wide range of upper harmonics. +The number of cycles as well as the frequency variation within one cycle are controlled +with 2 two-dimensional sliders labeled diff/nrsin, one for the startup, +one for the sustain part of a note. With slider start-amp the amplitude at +startup is controlled. With radio-button tone a doubled frequency can be mixed +in, which enhances audibility of low-frequency notes. +This instrument can sound like a rough piano, a guitar or a bass-guitar. +
green +Two waveforms as used for the red instrument are combined. The ratio of their frequencies +can be choosen between 1 and 3. A chorus effect is created by a controllable detuning +of the 2 waves. Beautiful sound. +
blue +Basicly a pulse. If the chorus checkbox is enabled, +then a second pulse with a slightly different frequency is added with the opposite +amplitude. The 2 signals cannot fully cancel each other, because they +are asymmetric. With the rich tone checkbox, pulses with 2 and 3 times the +basic frequency are added. Also a slider lowpass is provided. At higher values +of this control it does not act like a real lowpass filter, more like a formant shaper. +A limit on the duration of a note can be set with +slider dur limit (dur = duration), which is useful if long notes have been generated from MIDI input. +This instrument can have a pure, floating quality, or a full rich sound like a piano if +chorus as well as rich tone are enabled. Notice that if the duration limit is different from zero, +then staccato notes will sound like normal notes. +
purple +An additive synthesis instrument, like a Hammond organ. The sound is the sum +of sinus waves with a frequency of 1, 2, 3, 6 or 9 times the fundamental, +with separate controls for these harmonics during startup and sustain. +Duration of the startup is controllable. A slight distorsion in the time domain +can be added, which also can be modulated with a low frequency. +This instrument can sound very nice and mellow, or somewhat harsh. +
+

+Instruments can act 2 ways: with their "native" sound, or as a monophonic +synthesizer. Their control panel contains a box with choice native or msynth. +If the latter is enabled then a control panel with a lot of sliders pops up. See chapter +Mono Synthesizer. +

+All instruments have stereo sound. The stereo effect is created by delaying one +channel with 2 msec and decreasing its amplitude to 50%. Panning between left and +right channels can be controlled by script command pan:LL, pan:L, etc. +

+Each instrument can sound like one of the other instruments, using a button labeled eq?. +This is indicated by a colored line at the left side of the control panel. Repeated clicking +has the effect of cycling through the 6 different instrument colors. Notice that the amplitude +control of an instrument is done by its own slider. + + +

Reverb

+One instrument can have reverberation. The reverb panel +contains a button instr? that when clicked cycles through the +6 instrument colors. The horizontal value of the slider controls the amount of reverberation plus the "wetness", +which is the ratio of direct sound and delayed sound. The displayed value e.g. might be +'0.5 long', which means: the ratio of the delayed and the direct sound is 1 to 1, the delay is long. +If the vertical value of the slider is set to 1 or 2, then a phasing effect will be added. +

+Sounds with a very short attack time don't sound good with reverberation. + + +

Sampled instruments

+If the sampled notes checkbox is enabled, then a control panel of a sampled instrument +will appear. There are 2 modes, which can be choosen with the samples choice box. +In the first mode, the sounds are synthesized via a technique called "physical +modeling". The models are relatively simple, so the modification of parameters +can be performed in realtime. +

+In the second mode, sounds are read from files in WAVE format. The wave files are +read from the current directory or from /usr/share/amuc/samples. This search path +can be altered by a script command sample-dir:, see the Scripts +chapter. +A wave file name must start with a number, and end with the extension '.wav'. +The number of channels must be 1 (mono), the sampling rate must be 44100 Hz. +

+Of course you can use other files if you don't like the ones provided. +All kind of sounds can be used, for instance samples from real instruments or from human voices. +

+The control panels look as follows: + + + + + + + + + + + +
Physical model:           Wave file:
 
 
+

+In wave file mode, clicking the arrow-button will open a menu with wave file names. +The text is colored: black when found in the current directory, blue when found in +/usr/share/amuc/samples, else green. Notice that the list of wave files +is stored internally when they are needed for the first time, and that this list +is not refreshed automatically after a modification. That is what button +rescan wave files is for. +

+Sampled sounds can be heard 2 ways, dependent of the status of checkbox controlled pitch. +If this checkbox is off, then the pitch of the sound is independent of the score line where +the note was drawn. If the checkbox is enabled, then the pitch is dependent of the score line. +The reference line, where the pitch is the original one, is middle-C (which is the score line between +the upper and the lower staff). Only the sampling speed is modified, so increasing pitch yields +a shorter duration. +

+If checkbox just listen is enabled, then you will hear the sound from the wave file +when clicking its name. The sound of the instrument will not be modified however, and the menu will +stay on-screen. + + +

Central checkboxes and buttons

+Their functions are described elsewere. A short summary: +
+
score keys +
Opens the Scales and score keys window.
+
sampled notes +
Notes entered in a score panel will be of the sampled kind.
+
ignore set cmd's +
When the score at the bottom is played, then script set commands (which control the instrument sounds) +are in effect. If you don't want this, then check this button.
+
connect MIDI keyboard +
See chapter MIDI keyboard.
+
+

+ + +

The menu panel

+This contains 3 menus. +
+
File
+This menu looks as follows:
+
+As can be seen, the dialog panel sometimes is augmented with a small black triangle, +indicating that there is a choice menu available. The File menu contains the following items: +

+

+
load score file...
+Enables loading of a score file. In the dialog +panel (middle right) a file name must be specified. If you started Amuc by double-clicking +its icon, then the path to the file is relative to your home directory. If started +from the command line, the path is relative to the current directory. +

+After clicking the small ok button or hitting the enter key, the file will +be read. A list of available .sco files will appear if the black triangle is clicked. +This list will disappear if an item is clicked, or if the black triangle is clicked +a 2nd time. +

+Score files are conventionally saved with extension .sco. After loading a +score file the tunes that +are contained in the file will be listed in the tunes panel. +

+

add score file...
+Same as load, but keep already loaded tunes. +

+

save score file...
+The tunes in memory can be saved to file by clicking this button. +

+A score file is more or less human readable. +Modifications like renaming or copying of the tunes could be done with a +text editor. Also other modifications like e.g. re-coloring of all notes +could be done, as each parameter has a unique letter code. Tip: in your text +editor disenable line wrapping. +

+

read script...
+After clicking this button and supplying a file name in the dialog panel a script +file will be read and shown in the built-in editor. The script is executed, after this +the result will +appear in the score at the bottom. +An alert window will pop up if errors in the script are detected. +

+

save script...
+The script can be saved by choosing this menu item. +

+

current dir...
+For switching to a different directory. A list of available directories beneath +the current directory is shown after clicking the black triangle in the dialog panel. +The current directory always is displayed at current dir +halfway at the right side. +
+

+

Import/Export
+
+
save as WAVE...
+ save as MIDI...
+ save as postscript... +
Creating files in different formats: WAVE, MIDI or human-readable scores in +postscript format.
+See chapter Export WAVE file, Export MIDI file, +Export postscript file. +

+

read MIDI file... +
Import a MIDI file. See chapter Import MIDI file. +
+

+

Help
+
+
Manual
+The Amuc manual is in html format. Selecting the menu item will open a browser. +The default browser is Epiphany, which can be modified by editing the file .amucrc in your +home directory. +

+

About
+Shows my name and the URL of Amuc's website. +
+
+ + +

The buttons at the right side

+
+ +
new tune...
+After clicking this button, the dialog panel will ask for the name +for a new tune. This name will be added to the list in the tunes panel. If one +of the score panels is active, then it will be assigned to the new tune at that same moment. +

+

copy tune...
+The tune that has been selected in the tunes panel will be copied. +The dialog panel will ask for a name +for the new tune. If this is not an existing name, then the name will +be added to the list in the tunes panel. +

+

rename...
+To rename the tune that has been selected in the tunes panel. +

+

up +
down
+The tune that has been selected in the tunes panel can be moved up or down within the list. +

+

remove
+To remove the tune that has been selected in the tunes panel. +

+

clear tune
+This button affects the tune of the active score panel. It will be reset. +If no score panel is active, then hitting this button will reset +the complete score at the bottom, which is useful when manual commands are being used to populate it. +

+

cmd...
+The commands as listed in a script file also can be given manually, by typing +them in the dialog panel and clicking ok or by hitting +the 'enter' key. +

+Multiple commands can be given by separating them with a semicolon. +Don't forget the time: parameter after add and set commands. +Because the score at the bottom might be modified by an entered command, it will +always be redrawn after a command. Also the actions that are valid +at time 0 will be executed. +

+If the command in the +dialog panel is still valid, then clicking the ok button again will re-execute it. +

+Operations that are not provided by the available buttons sometimes can be +performed by giving an appropriate command. After reading the +script syntax chapter the following examples will be clear. +

+

+
Take tune tune1, shift all notes 4 units to the right: +
take tune1 time:0.4; put tune1 + +
Take tune tune1, lower it 5 semitones: +
take tune1 shift:-5; put tune1 + +
Omit notes between 1.0 and 1.4 from tune tune1: +
take tune1 to:1; take-nc tune1 from:1.4; put tune1 + +
Join tunes tune1 and tune2: +
take tune1; take-nc tune2 time:0; put tune1 +
+

+Of course, commands like these also could be typed in the script window, +possibly followed by an exit statement, which will interrupt the script execution. +

+

mod times
+With this button the current script file can be modified such that +a gap in the complete score is created, or that a part of it is removed. +The script in the built-in editor will be modified. +

+The positions of the starting and stopping marks in the complete score +control the operation. The time:n parameter of set +and add commands will be modified, as follows: +

+
If the starting mark is before the stopping mark: +
+All times greater or equal than the starting mark will be increased with +an amount equal to the distance between the starting and stopping mark. +
If the starting mark is after the stopping mark: +
(Don't mind the warning that will be issued)
+In case of set command: +All times greater or equal than the starting mark will be decreased with +an amount equal to the distance between the stopping and starting mark. +All times between the stopping and starting mark will get the value +of the stopping mark. +

+In case of add and take commands: +All times greater or equal than the starting mark will be decreased with +an amount equal to the distance between the stopping and starting mark. +All tunes starting between the stopping and starting mark will be removed. +

+

+Notice that if only relative timing (e.g. rt:-1) has been used, +then there are no time: parameters, so mod times will have no effect. +

+

run script
+This button should be clicked after modifications in the scores or in the script. The +complete score at the bottom will be redrawn. The set commands that are valid +at time 0 will be performed. If you don't want this, then enable checkbox +ignore set cmd's. +

+

play +
stop +
+Will play and stop the complete score. While playing, some controls +have an instantaneous effect (e.g. the tempo control and several +instrument settings), other controls have no effect. The stop +button works also for the 2 score panels. +

+Starting and stopping times can be controlled by clicking in the upper part of the score, +using the left or the right mouse button. Small red triangles then will appear. +

+While playing, sliders and checkboxes will be redrawn such that +they display their current value. Notice that set commands in scripts +are only executed when the complete score is played. If a tune in a score panel is +played, then the control values keep their current values, which can +be modified manually if needed. +

+

+ +

Oscilloscope

+This panel shows the contents of an internal audio buffer. Slider scale +controls the time scale of the displayed waveform. At low settings the +precize waveforms can be inspected, at higher settings the envelope of +the audio signals will be visible. + + +

Script editor

+The script editor is located at the right bottom of the window. Its +dimensions are increased by increasing the window size: +

+

+

+At the left side there is an area containing numbers, which indicate the number +of the measures where add commands have inserted a tune. +

+The script editor is a simple text editor, where the left mouse button +activates the text cursor, and the following keys behave as expected:
+

+arrow up, down,
+arrow left, right, (may shift the current line, if it is too long for the window)
+backspace,
+return,
+ctrl-U (clears the current line). +
+Text, selected in other windows can be pasted at the location of the cursor, +using the middle mouse button. +Set commands can be generated automatically. First select a color in +the color panel, then press ctrl-S. A complete set command will +be written to the line where the text cursor is located (if this line is empty) or +to the next line (if it's not empty). This also works if the sampled notes +checkbox is enabled. The short form of the set options is used (e.g. "stw" +instead of "start-wave"). +

+The script is executed with the run script button, +and saved with the save script button. If the capabilities of the built-in +editor are too limited, you better use a normal editor instead, and click the +read script... button afterwards. + + +

The complete score

+The score at the bottom is assembled from the available tunes. As mentioned +before, this is controlled by the script. The syntax is explained +in chapter Scripts. +

+At the bottom of the score the names of the tunes that were used as building blocks are shown. +Clicking on the play button (right middle) will start the playing, +during which the appearance of all controls is updated such that they show their +current value. +

+This score cannot be modified with the mouse, only starting and stopping times of the +playback can be entered. This is done by clicking the left and right mouse buttons +respectivily in the upper area of the score, and is indicated by small red triangles. +They can be undone by clicking at the same point a 2nd time, or by clicking the middle +mouse button. +

+Notice that always before playing all script set +commands are executed, also when the playing starts not at zero. +However, when the ignore set cmd's checkbox is enabled, then the set commands will +not be executed. This is handy if you want to experiment with different instrument +settings. +

+The note info button works also for this score. + + +

Zoomed-out score view

+If the checkbox labeled zoom is enabled, then the notes in the score panel +will be enlarged 3 times in the horizontal +direction. The timing of the individual notes can be modified such that at the beginning +of a note an extra delay is introduced, as well as at the end of a note. +In the following screenshots you can see: +
  • 8 notes in 4 time units +
  • 8 notes in 6 time units +
  • 3 notes in 4 time units +
  • 3 notes in 8 time units +
  • a fast tremolo +

    +No zoom, normal display:

    + +

    +The same notes, zoomed, display mode = timing:

    + +

    +Editing the start end the end of notes in the zoomed window is simple. First set the +display mode to timing. Then, clicking on the start of a +note with the left mouse button will introduce a delay of 1, 2, 0, ... times +1/3 note unit. Clicking with the right button has the same effect at the end +of the note. Clicking somewhere in the middle of a long note has a visible +but not an audible effect. + + +

    Scales and score keys

    +If the score keys button is clicked then a window pops up: +

    +

    +

    +The keys are listed in the leftside panel. The columns +denote respectively: the major keys, the minor keys, the number of flats or sharps, +and the sequence number of the notes relative to base tone (the tonic). +This last column is updated if a new base tone is choosen. +

    +The circle-of-fifths figure at the bottom indicates with a red dot the choosen key. +

    +If the set key button +is clicked, then key signatures will be drawn in the active score panel +(the score panel with button active enabled). So if e.g. +the base tone F is selected, then a flat signature will appear in front of all score lines +for note B. When clicking the force key button, all notes in the active score panel +will get the key signature of their score line. +

    +At the right side a list is shown of a collection of scales and chords. If an item is clicked, then +in the active score (provided it is set to the piano-key mode), +the lines belonging to the particular scale or chord are drawn in grey, the tonic +in blue. The lines not belonging to the scale or chord are empty. +

    +The list items appear in 3 different colors: +

      +
    • Red: the line positions are dependent on the score key. +So e.g. if the score key is Db, then the 'major' scale will start at the note Db. +The name 'Db major' then will be drawn at the bottom of the score. +Checkbox local dep is off by default. + +
    • Blue: the line positions are dependent on the choosen item in the keys list. +So e.g. if the choosen key is Db, then the C7 chord will be interpreted as Db7. +This name will be drawn at the bottom of the score. +Checkbox local dep is on by default. + +
    • Black: the line positions are constant. +
    +The operation of a red or blue item can also be controlled manually with the local dep checkbox. +

    +If the same score is assigned to both score panels, then the first could display a scale and +the other a chord. Then you can verify wether the combination conforms to the rules of +jazz or classic harmony. +

    +The scales and chords are specified in a file chords-and-scales, which should be +present in the current directory or in /usr/share/amuc. This file looks like: +

    + + +
    +"white keys"            0,2,4,5,7,9,11  no
    +"major"                 0,2,4,5,7,9,11  scale
    +"minor"                 0,2,3,5,7,9,10  scale
    +"blues scale"           0,3,5,6,7,10    scale
    +"major pentatonic"      0,2,4,7,9       scale
    +"minor pentatonic"      0,3,5,7,10      scale
    +"diminished"            0,1,3,4,6,7,9,10 scale
    +"whole tone"            0,2,4,6,8,10    scale
    +"C7 (dominant 7th)"     0,4,7,10        chord
    +"C- (minor 7th)"        0,3,7,10        chord
    +"C/0 (half-diminished)" 0,3,6,10        chord
    +"Co (diminished)"       0,3,6,9         chord
    +"C7sus4 (suspended 4th)" 0,5,7,10       chord
    +
    +

    +The 2nd column indicates for each note the number of semitones from the root. The +last column can contain scale (dependent on score key, displayed in red), +chord (dependent on selected key in keys window, displayed in blue), +or no (constant, displayed in black). +

    +Some chords span more then 1 octave, e.g. the C11 chord. These will be displayed +slightly different: one base line in blue, the lines of the 1st octave in black +and the lines in the 2nd octave in green. If the chord name is clicked a second +time, then the displayed lines will shift over 1 octave. +

    +Good chord and scale combinations can be found in books. +Here is useful table for jazz and pop music. +The following screenshot shows the result of an attempt to find good chords with a melody. +The melody (upper score panel) is in Bb minor. According to the table this could +be combined with Bbmaj7 chords. This was done in the lower panel, and indeed: +together they sound good. (Of course, harmonizing a melody involves much more +then conforming to a table!) +

    +

    + + +

    Export WAVE file

    +After selecting menu item Import/Export -> save as WAVE..., the dialog panel will ask for +a file name. After this, a stereo WAVE file will be generated. +The file is located in the current directory. +

    + +

    Export MIDI file

    +After selecting menu item Import/Export -> save as midi..., +the dialog panel will ask for a file name. Then a MIDI file will be generated in the current directory. +

    +The default mapping for real-time instruments is as follows:
    +

    + + + + + + + +
    black: MIDI instrument 6, electric piano 2
    red: MIDI instrument 25, guitar
    green: MIDI instrument 22, accordion
    blue: MIDI instrument 1, piano
    purple: MIDI instrument 17, drawbar organ
    brown: MIDI instrument 8, clavinet
    +
    +The default mapping for sampled instruments is:
    +
    + + + + + + + +
    black: MIDI percussion instrument 36, bass drum
    red: MIDI percussion instrument 47, low-mid tom
    green: MIDI percussion instrument 38, acoustic snare
    blue: MIDI percussion instrument 46, open hi-hat
    brown: MIDI percussion instrument 42, closed hi-hat
    purple: MIDI percussion instrument 62, mute high conga
    +
    +The mapping can be overruled by script commands, using e.g. out-par midi-instr-0:. +These commands can be timed, so the instrument mapping can change at any choosen moment. +See chapter Scripts. +

    +The created MIDI file is a General MIDI (GM) file, format 1 (which means: one track +for each channel). The channels are named or renamed according to their MIDI instrument. + +

    Export Postscript file

    +Human-readable scores can be generated by selecting menu item Import/Export -> save as postscript.... +In the following picture the rendering of a postscript file by Evince is shown. +The depicted page is from a piece written for big band. +

    +

    +

    +The generated scores are not perfect. Specifically, triplets are not rendered +as they should: marked with -3-. The reason is that in Amuc no syntax is +available to indicate which notes belong to a triplet. +

    +Percussion instruments get their own stave, they are rendered as 1/8 notes. +

    +The translation to postscript goes via a format called ABC. More information is available +at the ABC homepage: +http://www.walshaw.plus.com/abc/ +

    +The code in ABC format, not translated to postscript, also can be written to a file. +This happens if you specify a file name ending with .abc. +If needed you can modify this file (e.g. by modifying the clef or +the unit note length, or by adding triplets), +and then translate it to postscript by program Abcm2ps, +available in directory src-abcm2ps. +For further details see chapter +Abcm2ps
    +

    + +

    Read MIDI file

    +An awful lot of free MIDI files are available on the internet. You can import +them in Amuc, learn from them and modify them. +

    +If, after selecting menu item Import/Export -> read MIDI file... and hitting ok +a MIDI file is read +for the first time, then a file with the same base name and extension .gm-map +will be created. This file will control the mapping from MIDI instruments to Amuc instruments. +

    +A mapping file might look as follows: +

    + + +
    +set format:2 key:C acc:flat
    +channel=1  time=0  "Soprano Sax" (6)              black midi 0
    +channel=1  time=72 "Electric Piano 2" (6)         black midi 0
    +channel=2  time=12 "Acoustic Grand Piano" (1)     black midi 0
    +channel=3  time=24 "Reed Organ" (21)              black midi 0
    +channel=4  time=36 "Drawbar Organ" (17)           black midi 0
    +channel=10 time=0  PERCUSSION                           midi 0
    +
    +

    +The first line is for setting some parameters.
    +format:2 the current format of this file.
    +key:C sets the key signature for the scores, the C +can be replaced by another key.
    +acc:flat controls how MIDI notes are interpreted, +e.g. a MIDI note number 61 could be a C# or a Db. This acc is short for 'accidental'. +It can have value sharp or flat. +

    +There are 2 optional parameters:
    +shift:n to shift all notes n semi-tones.
    +tinc:n (short for 'time increase') to increment note length's with a factor n. +This may be a real number. The default is 1. +

    +After the first line, the columns have the following meaning: +

      +
    1. MIDI channel
    2. +
    3. The time that this channel was assigned to a MIDI instrument (by a 'program change' message)
    4. +
    5. The assigned MIDI instrument
    6. +
    7. The General MIDI number of this instrument
    8. +
    9. The Amuc instrument to which this MIDI instrument will be mapped
    10. +
    11. The name of the tune to which the MIDI channel will be written
    12. +
    13. The group number of this tune
    14. +
    +You are supposed to edit the last 3 colums before hitting the ok button for the 2nd time. +If different group numbers (0, 1 or 2) are assigned to the same tune, +then a warning will be issued. If the frequency of a channel should be shifted e.g. 6 semi-tones, then add +an extra +6 or -6 at the end. The mapping file now might look as follows: +

    + + +
    +set format:2 key:F acc:flat
    +channel=1  time=0  "Soprano Sax" (6)              black  saxes 0
    +channel=1  time=72 "Electric Piano 2" (6)         blue   piano 0
    +channel=2  time=12 "Acoustic Grand Piano" (1)     black  piano 0
    +channel=3  time=24 "Acoustic Bass" (33)           red    bass  0 +12
    +channel=4  time=36 "Drawbar Organ" (17)           purple organ 0
    +channel=10 time=0  PERCUSSION                            drums 0
    +
    +

    +The mapping of MIDI percussion instruments +is fixed. You might alter it by modifying file midi-in.cpp in the source code +and recompile. +

    +In standard GM files the percussion instruments are assigned to channel 10. +An extra line in the mapping file will appear with instrument name PERCUSSION, +even if no explicit assignment to this channel is present in the MIDI file. + + +

    Mono Synthesizer

    +Instruments can double as a traditional mono synthesizer. If choice +msynth in an instrument control panel is clicked, then the following window +will appear. +

    +

    +

    +The mono synthesizer has been carefully crafted to provide good and versatile sounds +whose control is not too complicated. Conceptually the different parts work together +as follows: the output of 2 oscillators, VCO1 and VCO2, and a pink noise source +are added in MIXER. The resulting signal is controlled by an +envelope generator, EG1, and by a bandpass/lowpass/highpass filter, VCF. A low-frequency +oscillator, LFO, and a 2nd envelope generator, EG2 can modulate the amplitude, the pitch +and the filter cutoff. A third oscillator, VCO3, can modulate the amplitude and the filter cutoff. +

    +A short listing of the different parts: +

    +
    VCO1
    +Voltage controlled oscillator, with 3 wave forms. Can be detuned to get +richer sounds. +The slider waveform controls the shape of the pulse or the triangle wave. +With the FM VCO2 slider the VCO2 wave can be FM modulated. Especially if the +VCO2 pitch mult(iplier) is 2 or more this yields interesting sounds. +

    +A special effect is created if the trigger VCO2 checkbox is enabled. Then +VCO1 will trigger VCO2 at each cycle, so if you listen to VCO2, it will seem to +have the frequency of VCO1. At each cycle of VCO1 one or more cycles of VCO2 will +be heared, dependent on the pitch mult. slider of VCO2, which will create +a formant peak in the sound spectrum. +

    +

    VCO2
    +The 2nd voltage controlled oscillator, also with 3 wave forms. +The pitch can be shifted from -1 to +8 octaves. +

    +

    VCO3
    +This voltage controlled oscillator is used for modulation. +The pitch can be shifted from -1 to +8 octaves. +This VCO can modulate the amplitude and/or the cutoff frequency of the VCF, +which might yield somewhat rough sounds. If the ampl. mod. slider is in its +highest position, then ring modulation will occur. +

    +

    LFO
    +The low-frequency oscillator can be used to modulate the amplitude and/or +the VCO2 frequency and/or the cutoff frequency of the VCF. If the ampl. mod. slider is in its +highest position, then ring modulation will occur. +

    +Two extra kinds of wave are provided: white noise, and a combination of 3 sinusoids called flutter. +Their frequency range can be choosen with the frequency slider. +

    +

    MIXER
    +The 1st and 2nd slider control the amplitude of VCO1 and VCO2. With the 3rd slider a pink noise signal +can be added. +

    +

    EG1
    +The first envelope generator controls the course of the amplitude. +Four sliders control a classic ADSR envelope. +The LFO effect slider: if value = 0, then the effect of the LFO waveform is +independent from the EG1 waveform. +If value = 2, then the EG1 waveform will modulate the amplitude of the +LFO waveform, in order to create a natural-sounding modulation. +

    +

    PORTAMENTO
    +If 2 notes overlap with a minimum of 1 time unit, then portamento will +occur if this slider value is non-zero. +

    +

    EG2
    +Similar to EG1. It does not modify the amplitude however, it modulates the frequency +(pitch mod.) and/or the VCF cutoff frequency +(filter mod.). +At the start of a note the envelope generators are not reset, they keep the momentary value +from the preceeding note. This yields a more natural sounding result compared to the case +where each note would start with envelopes starting at zero. +

    +The characteristics of the envelope generators are both displayed. The x-axis of these curves +are scaled such that the envelope of EG1 fits more or less inside its window. Between the decay- and the +release phase a constant time-interval is inserted, with a grey-colored background. +

    +

    VCF
    +This is the voltage-controlled bandpass/lowpass/highpass filter. There is a choice between 2nd and 4th order. +The output amplitude can be (soft) clipped, which is useful at high resonance values. +

    +Because the filter is controlled by many sources, you easily can loose track. Because of this, a small display +has been provided. Two black lines show the frequency of VCO1 and VCO2, a red line shows +the cutoff frequency of the VCF. +

    +

    AMPLITUDE
    +The master amplitude control. +

    +

    load patches
    +A file named monosynth-patches will be read. If it is found in the +current directory, then the patch names will be written in black, +if it is found in directory /usr/share/amuc, then in blue. +A patch can be effectuated by clicking on its name. +
    save this patch
    +After modifying a patch with the slider controls, it can be saved in memory. +The monosynth-patches file is not altered. +
    add current patch
    +The current patch is added to the list. It will be named NEW. Later you can +modify monosynth-patches with a text editor and give it a meaningful name. +
    save patches
    +Write a new file monosynth-patches to the current directory. +
    +If a synthesizer is instantiated for the first time it will be initialized +with the default patch, which is a fat bass sound. Actually, setting the sound +of a synthesizer is a delicate process, but it helps if you know what you are +doing. Sometimes a patch sounds good for low pitches but not for higher notes, +or vice-versa. +

    +At the left bottom corner there is a button labeled eq?. If it is clicked +then a line is drawn at the left side with one of the instrument colors. This +means that this synthesizer will use the patch of that other synthesizer. +

    +Notice the way that the synthesizer control window is displayed. If a different +instrument is choosen in the color panel and the instrument happens to be +a synthesizer, this is only visible by the fact that the msynth label +in the choice box is enabled, the synthesizer control window is not visible however. +This is done because this window is rather intrusive. You can make it visible by clicking +the msynth label. +

    +The control string for a mono synthesizer is rather long, e.g. +F5,00201,702,300,5040500,050,0aa70,0,088950,92010,8
    +so you will create it inside a script +using the ctrl-S key. Still for small modifications it is handy to set the parameters manually. +The numbers reflect the positions of sliders, choice- and check boxes. The fields are ordered +as follows: +

    + + + + + + + + + + +
    Synthesizer parameters
    +
    field nr. 12345678910
    nameformatVCO1VCO2VCO3LFOMIXEREG1PORTAMENTOEG2VCFAMPLITUDE
    exampleF5,00201,702,300,5040500,050,0aa70,0,088950,92010,8
    + + +

    Scripts

    +A script file (conventionally with .scr extension) consists of separate +commands, each ending in a newline or a semicolon. Parameters for the commands +have the form keyword:number. The comment sign is #, after this character +the rest of the line will be ignored. +The basic commands are as follows. +

    + + + + + + + + + + + + + + + + + + + + + + +
    Basic script commands
    +
    take tune +Clear the internal score buffer, then copy tune tune to it. +
    take-nc tune +Copy tune tune to the internal buffer without first clearing it. +
    add +Add the buffer contents to the complete score. +
    add tune +Add tune tune directly to the complete score. +
    put tune +If tune is found in the list of tunes, then copy the contents of the +buffer to this tune. +If tune is not found, then first create it. +
    set +Set parameters. +
    out-par +Parameters for output (postscript, MIDI). +
    sample-dir:d1,d2,... +Specifies the directories where sample wave files are located. The current directory is +as usual '.', the standard Amuc data directory is /usr/share/amuc. +
    extended-syntax +Enable extra (undocumented) features: amplitude and "eq?" settings can be controlled +separately for each instrument group. +
    exit +Skip rest of script. +
    +

    +Most basic commands can have parameters. The add, take and take-nc +commands take the same parameters, as follows: +

    + + + + + + + + + + + + + + +
    Parameters for add, take +and take-nc
    +
    time:number +Add at a certain time.
    +number has one of 2 forms:
    +  n - indicates the measure number
    +  n.m - e.g. 3.2 indicates the 2nd note-unit in measure number 3.
    +Multiple times are also supported, e.g. time:2.4,9 means that +the tune will be added at the start of measures 2.4 and 9.
    +If no time:n command is given, then the current time is supposed, which is defined +as the latest stopping time of all tunes added until now. +
    rt:number +Relative time: add with respect to the current time. +
    shift:n +Shift all notes n semitones up (if n is positive) or down (if n is negative). +If the score is in a key containing flats, then accidentals (the black keys of a piano) +will be flats. If the score is in a key containing sharps, then they will be sharps. +
    raise:n +Shift all notes n lines up or down. The sign of the notes is not +copied, like when moving selected notes with the mouse. +
    from:n to:m +Take an interval. n and m are numbers like in the time: parameter. +
    color:s +Recolor all copied notes. s = black, red, green, blue, brown, purple. +
    +

    + +The out-par command specifies parameters used for postscript and MIDI output. +

    + + + + + + + + + + + + + + + + + + + + +
    +
    Parameters for out-par
    +
    Valid values +
    time:number +The midi-instr and midi-perc parameters will be effective at the specified time, +see the set command. +  +
    midi-instr-g:n1,n2,n3,n4,n5 +Set mapping from instruments (black, red, blue, green, purple, brown) to midi instruments. +A value 0 means: no mapping.
    +g is the group number of a score, as choosen by the gr# button in a score panel.
    +The default mapping is midi-instr-0:6,25,21,1,5,17. +
    0 ≤ g ≤ 2
    +1 ≤ n1 ≤ 128, etc. (according to the GM standard) +
    midi-perc-g:n1,n2,n3,n4,n5 +Set mapping from sampled instruments (black, red, blue, green, purple, brown) to midi instruments +at midi channel 10. A value 0 means: no mapping.
    +The default mapping is midi-perc-0:36,47,38,46,42,62 +
    0 ≤ g ≤ 2
    +35 ≤ n1 ≤ 81, etc. (according to the GM standard) +
    key:k +Set the key for the complete score. Used +for postcript output. The k parameter should be +one of the keys as shown in the score keys panel. +k = C, c, ... +
    nupq:n +Set note-units-per-quarter-note. Used +for postcript- and for MIDI output. Default value: 4. +  +
    transp-g:n1,n2,n3,n4,n5,n6 +Some real instruments are sounding lower or higher then expected. E.g. when ad alto saxophone +plays a C then an Bb is heard. The written score for these instruments should thus be +transposed, which can be done with this statement. The value of n1 etc. +is equal to the desired shift in semitones. The key will be modified accordingly.
    +g is the group number of a score, as choosen by the gr# button in a score panel. +
    0 ≤ g ≤ 2
    +-24 ≤ n1 ≤ 24, etc. +
    single-voice +Useful for scores for the individual instruments of an orchestra. This option has the +effect that multi-measure rests are replaced by thick lines plus a number, and that a warning +is issued if an instrument plays not a single tone. Real chords (all +chord tones of equal length) will get only a warning at first occurance.

    +Use this option only to generate an .abc +file, then use this file with a customized header as input for the Abcm2ps tool. +

      +
    abc-header:header-file +The default header statements are replaced by the contents of header-file. +See chapter Export postscript file for an example. +  +
    annotate:n1,n2,n3,... +Print annotations 'A', 'B', 'C', ... above measures n1, n2, n3, ... +  +
    +

    +The set command can take many parameters. Notice that for the start +of tones two different words are used: "attack" specifies the amplitude +at the beginning of a tone, "startup" specifies the waveform at the beginning of a tone. +In the chapter about script editing was described how to generate set commands +automatically. + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    Parameters for set
    +
    Valid values
    for n, m etc. +
    short form +
    time:value +Succeeding parameters are valid after this time.
    +value has one of 2 forms:
    +  n - indicates the measure number
    +  n.m - e.g. 3.2 indicates the 2nd note-unit in measure number 3.
    +This parameter can be given more then once on the same line, the last read value is valid.
    +Multiple times (with comma-separated numbers) are supported.
    +If no time:n parameter is given, then the current time (as used for the add +command) is supposed. +
    value >= 0 +  +
    rt:number +Relative time: set with respect to the current time. +  +  +
    tempo:n +Set the tempo. +n > 10 +  +
    + + + + + + + +
    black ampl:n
    red -
    green -
    blue -
    purple-
    brown -
    +
    Set instrument amplitude in 'nat' mode. +0 ≤ n ≤ 9 +a: +
    black wave-ampl:n
    +etc. +
    Set amplitude of sampled instrument, in both 'phys model' and 'wave file' mode. +0 ≤ n ≤ 7 +wa: +
    + + + + + +
    black attack:n
    green -
    blue -
    brown -
    +
    Set instrument attack. +0 ≤ n ≤ 5 +at: +
    + + + + + + + +
    black decay:n
    red -
    green -
    blue -
    purple-
    brown -
    +
    Set instrument decay. +0 ≤ n ≤ 5 +dc: +
    +red start-wave:n,m
    +red sustain-wave:n,m +
    Set waveform during startup or sustain. +The waveform will consist of m +sinuses, which vary in frequency dependent on the value of n +(value 1: small difference, tone with a strong formant; 5: big difference, +bright sounding tone). +1 ≤ n ≤ 5
    2 ≤ m ≤ 4 +
    stw: +
    red tone:n +Set tone (0: clean, 1: 2nd harmonic added). +0 ≤ n ≤ 1 +  +
    red startup:n +Set duration of startup wave. +0 ≤ n ≤ 5 +su: +
    red start-amp:n +Set amplitude at startup.
    +n=0 : soft startup
    +n=3 : loud startup +
    0 ≤ n ≤ 3 +sa: +
    green wave1:n,m
    +green wave2:n,m +
    Set waveforms. +The waveform will consist of m +sinuses, which vary in frequency dependent on the value of n +(value 1: small difference, tone with a strong formant; 5: big difference, +bright sounding tone). +1 ≤ n ≤ 4
    2 ≤ m ≤ 4 +
    w1:
    w2: +
    green freq-ratio:n +Set frequency ratio wave2/wave1, between 1 and 3. +0 ≤ n ≤ 3 +fr: +
    green chorus:n +Set chorus effect. +0 ≤ n ≤ 3 +ch: +
    blue rich:b +Set extra harmonics. +b = on, off +  +
    blue chorus:b +Set chorus effect. +b = on, off +ch: +
    blue lowpass:n
    +
    Lowpass filter/formant shaper.
    +n = 0: no filtering, n = 4: strong formants.
    +
    0 ≤ n ≤ 4 +lp: +
    blue dur-limit:n
    +red dur-limit:n +
    Limit note duration.
    +n = 0: no limit.
    +n = 1,2,3,4: limit = 1,2,4,8 note units +
    0 ≤ n ≤ 4 +dl: +
    +black fm:n,m
    +brown fm:n,m +
    Set FM parameters. The actual values for the +modulation index and for the ratio modulation/carrier frequency, +are as displayed with the fm freq/index slider. +-1 ≤ n ≤ 7
    0 ≤ m ≤ 7 +
      +
    +black modmod:n,m
    +brown modmod:n,m +
    Modulate the amount of fm modulation with a low frequency signal. The n parameter +controls the value, the m parameter the frequency of this signal. +0 ≤ n ≤ 5
    0 ≤ m ≤ 3 +
    mm: +
    +black detune:n
    +brown detune:n +
    Set detuning of modulating frequency. +0 ≤ n ≤ 5 +dt: +
    purple start-harm:
      n1,n2,n3,n4,n5 +
    Set startup harmonics. +0 ≤ n1 ≤ 3, etc. +sth: +
    purple sustain-harm:
      n1,n2,n3,n4,n5 +
    Set sustain harmonics. +0 ≤ n1 ≤ 3, etc. +suh: +
    purple startup:n +Set startup duration. +0 ≤ n ≤ 5 +su: +
    purple sound:n +Set the sound for this instrument:
    +0: normal, 1:chorus, 2:distorsion, 3: fluctuating distorsion. +
    0 ≤ n ≤ 3 +snd: +
    + + + + + + + +
    black synth:F5,...
    red -
    blue -
    green -
    purple-
    brown -
    +
    Turns the instrument into a mono synthesizer. After the F5, header about 40 +single-digit numbers must be given. Though it's not impossible to enter these numbers manually, +you normally will use the ctrl-S key in the script editor. See chapter +Mono Synthesizer for details. +  +  +
    black pan:s
    etc. +
    Set stereo panning of instrument in native mode. +(Note: loc: is similar but obsolete).
    +If s = L or R, then pan = medium, and one channel is attenuated and delayed 2 ms. +
    s = LL, L, M, R, RR +  +
    black sampled-pan:s
    etc. +
    Set stereo panning of instrument in sampled mode. +(Note: sampled-loc: is similar but obsolete) +s = LL, L, M, R, RR +span: +
    black reverb:n1,n2
    +etc. +
    Set reverb (n1 = 0: no reverb, 4: max reverb) and phasing (n2 = 0: no phasing, n2 = 2: fast phasing). +Also works if in mono synthesizer mode. Only 1 instrument can have reverb. +0 ≤ n1 ≤ 4
    +0 ≤ n2 ≤ 2 +
    rev: +
    black mode:s
    etc. +
    mode:msynth - instrument will be a mono synthesizer
    + mode:nat - native instrument +
    s = msynth, native (or: m, n) +m: +
    black eq:s
    etc. +
    If in 'nat' mode, play the sound of a different instrument. +
    +NB! The pan: and the ampl: parameters are not affected.
    +(This isn't provided for sampled instruments.) +
    s = black, red, green, blue, brown, purple +  +
    black ms-eq:s
    etc. +
    If in 'msynth' mode, use the patch of a different instrument. +s = black, red, green, blue, brown, purple +  +
    black phys-mod:n1,n2,n3,n4
    etc. +
    Set sampled instrument sound in 'phys model' mode. The values are: speed, tension, decay, distorsion on/off + +1 ≤ n1 ≤ 5
    +1 ≤ n2 ≤ 5
    +1 ≤ n3 ≤ 5
    +0 ≤ n4 ≤ 1
    +
    phm: +
    black wavefile:n
    etc. +
    Set sampled instrument in 'wave file' mode. Wave files should start with a number. The n +parameter specifies this number.
    +Notice that after this command, both time stretch and pitch shift will be reset to 0. +
      +wf: +
    black time-stretch:n
    +etc. +
    Set time stretching of sampled instrument (-3: 50%, 3: 200%). +-3 ≤ n ≤ 3 +<>ts: +
    black pitch-shift:n
    +etc. +
    Set pitch shift of sampled instrument, in semitones. +-12 ≤ n ≤ 12 +ps: +
    black ctr-pitch:n
    +etc. +
    Set pitch control of sampled instrument, in both 'phys model' and 'wave file' mode. +n = on, off +cpit: +
    +

    +The colors in statements after a set need to be mentioned only once per line, +they stay valid for the rest of the line. +

    +Notice the different semantics of the time:n parameter. If it follows an +add, take or take-nc command, it is valid for the whole line; +if it follows a set command, it is valid for the statements after it. +

    +The options in the above table are in "long" format. Also a "short" format is +available, e.g. stw: instead of start-wave:. The short format +is used when the set command has been generated automatically (by pressing +ctrl-S in the script edit window). +

    +The pan: and span: parameters have no GUI equivalent. +

    +The eq: +parameter is useful to quickly investigate the impact of using a different instrument, +or for turning 2 or more mono synthesizers into a polyphonic synth. +It is also useful for postscript output for an instrument that is played with 2 hands, +like a piano. The left and right-hand parts then can be given different colors, and +still sound the same. +

    +The characteristics of the "physical modeling" instruments and the synthesizers +are set all in one command. This may not be +very convenient, the reason is that their complete waveform is calculated in one go. +

    +If tune in the list of basic commands is equal to the name of the complete score, then that +score is taken as input. The name of the complete score is equal to the score file name, minus its +extension. This way you can easily split the complete score in several big parts, that can be +processed once more. As an example, the following could be added to dance.scr: +

    + + + +
    +take dance to:30;         put part1
    +take dance from:30 to:45; put part2
    +take dance from:45;       put part3
    +
    +

    +After saving to another file and removing the original tunes from this file, you have a +restructured and simplified representation of the original dance.sco. + + +

    MIDI controllers

    +

    Connecting a MIDI keyboard

    +If a compatible (USB) MIDI keyboard is available, then +this can be used to generate a tune. See the README file for details about connecting +such a keyboard. +

    +Enabling the connect MIDI keyboard +checkbox will create the actual connection. If the +checkbox turns to the disenabled state immediately, then something has gone wrong and +an alert message will appear. If a connection via Jack has been choosen, then don't +forget to establish the actual connection, e.g. in the control panel of qjackctl. +The MIDI keyboard input for Amuc will show up under the ALSA tab. +

    +The only MIDI codes that are used by the program are note-on and note-off. What you hear +while playing is the instrument choosen in the color panel. +The timbre and the amplitude can be controlled, not the attack and decay. +

    +You will only hear it if some tune is playing. The reason +for this restriction is that it is hardly useful to enter played notes if you don't listen +to an existing tune or rhythm track at the same moment. +In order to create a tune it is best to first create a small, +rhythmic tune, then click the play button with repeat enabled. Then play what you +want on the MIDI keyboard, +and if you're done, click stop. At that moment a new tune +called 'keyboard' will be added to the list in the tunes panel. +This new tune can be renamed, assigned to a score panel, edited, re-colored, etcetera. + + +

    Companion tools

    + + +

    Wav2score - from wave file to score

    +Pianists who can improvise and want to enter their music into Amuc are lucky: keyboard +instruments usually have a MIDI output. But sax, horn or flute players, what can they do? +It is rather difficult but not impossible to translate recorded waveforms to notes, and +that is what Wav2score is made for. It translates an audio file (WAVE format, 44100Hz +sampling rate, mono or stereo) from one single-voice instrument into a score file to be imported by Amuc. +There it can be post-processed, mapped to different sounds, shifted in frequency or tempo, etcetera. +

    +The translation works as follows. A fast-fourier transform (FFT) is applied to the data +from the wave file, in order to get its time-variant frequency spectrum. A FFT needs +discrete chunks of data, called FFT data-windows. Short windows yield precision in the time +domain but cannot handle low frequencies, so there is always a compromise. Wav2score uses +overlapping data-windows, each multiplied with a Hamming window. In case of stereo +input, both channels are FFT'ed separately in order to allow phase differences between +the channels. +

    +The program can be used with or without a graphical user interface. With a GUI it looks +as follows. +

    + + +

    +Without a GUI the input file is handled piece by piece, but with a GUI it is read in its +full length and stored in memory. It is shown in the INPUT WAVE panel. Clicking in +this panel controls the red line, which marks the beginning of one FFT window, drawn +separately in the bottom panel. Also the fine - and course sliders get the right value. The +red line and the state of the sliders are always in agreement. +

    +The frequency spectrum of the FFT window is shown in the top panel. The algorithm implemented +in Wav2score searches for at most 3 equidistant frequency peaks. If these have been found +then colored triangles are drawn above the peaks, and a note is created. The MIDI number +of the note is shown in the INFO panel (where middle C = 261.6Hz = midi number 60). +The current midi number is shown between square brackets. +

    +You can listen to the wave file, using the PLAY button. The starting point is +at the red line. This button becomes a stop button while playing. +

    +The Wav2score GUI is only meant for investigation, the options are set at the command line. +However, the frequency as found by the program for each FFT sample can be overruled +manually. With the middle mouse button the lowest appropriate spectrum peak in the FFT window +can be clicked. Then the program calculates an exact peak location using the 2 neigbouring peaks, and, if +succesful, a black triangle will appear above the peak. Also small black triangles will be drawn +above every location where harmonics of the fundamental frequency are expected. The list of calculated MIDI numbers in +the info panel will be updated. +

    +Clicking the left or right mouse button inside the FFT window you can access the previous +or the next FFT sample. +

    +If button score display is clicked, then a small window will show a visual representation of +the generated notes. The current fft sample is in the grey area. Unresolved notes are depicted as a +red-colored line. +

    +The program is started from the terminal, as follows: +

    +    wav2score [options] <wave-file>
    +
    +The output score file, to be imported by Amuc, is fft.sco. The options: +

    + + + + + + + + + + + + + + + + + + + +
    -h + Print usage info and exit. +
    -nogui + Use command-line interface. (The old -gui option is deprecated.) +
    -win nr + FFT window = nr samples. Values between 512 and 8192, default: 2048.
    + If the given value is not a power of 2, then internally + it is rounded to the next-higher power of 2.
    + Small values yield better time accuracy, bigger values yield enhanced low-frequency resolution. + Samples are token at 44100Hz. +
    -min nr + Midi number of lowest fundamental (default: 0). +
    -max nr + Midi number of highest harmonic (default: dependent on -win parameter). +
    -ch R|L + If stereo, then select right or left channel. +
    -c nr + MIDI number for middle-C, default: 48. E.g. -c 36 yields notes + 1 octave lower. Notice that also floating values are permitted, e.g. -c 48.5 raises the notes + 0.5 semi-tone upwards. (The default value equals 48 because in Amuc middle-C is 130.8Hz). +
    -th nr + Threshold for spectrum peaks, default: 0.2
    + Use e.g. 0.9 if only the highest spectrum peak should be evaluated. +
    -dur nr + Note duration increment factor, default: 1.0
    + Use a bigger value if also fast notes must be handled correctly. +
    -es + Use each sample. Samples/note-duration factor such that each valid sample yields 1 note unit. +
    -signs hi|lo + Preference for sharp or flat accidentals in output score file. +
    +

    +The manually found frequencies can be stored in a configuration file (with +extension .w2s), by clicking the write config file button. +This config file will be read automatically when you process the same wave file +later. You don't have to supply parameters then, because the stored values +in the config file take precedence. If you want to try different values, then +modify them in the config file.
    +This feature is not available in command-line mode. +

    +Don't expect wonders from Wav2score. For most wave files you will have to try +different values for the -win and -th options. In case of fast notes a longer -dur +value will be needed. In the FFT WINDOW panel 3 or 4 full waves must be visible +in order to get the right frequency. +Usually the generated score file will need heavy editing to be useful, especially +if you only whistled or hummed the tune. Manually inserting frequencies takes a lot +of patience, but is doable. A big data-window size (e.g. -win 8000) means +less work to do. + + +

    Abcm2ps - from ABC file to postscript

    +Also provided is a full-blown translator from files in the ABC format towards +human-readable scores in postscript. +There are thousands of ABC files available on the internet. The nice thing about the +ABC format is that it is user- as well as computer-readable. +To learn more about Abcm2ps you can +visit http://moinejf.free.fr/. +

    +The dimensions +of the generated score notes are controlled by a format file. +A different format file can be specified by calling Abcm2ps with option -F. +

    +The original Abcm2ps source files were ported to C++ and simplified somewhat. Options can be seen +by calling the program with option -h. A couple of format files is provided in the +src-abcm2ps directory. The values in file default.fmt are equal to the built-in default +values of this Abcm2ps version. +

    +To get an impression of what's possible, we show the rendering of an organ concerto, +file org-concerto.scr from the distribution. The start of the generated, +unmodified ABC file and its result are as follows: +

    + +
    +
    +X:1
    +T:org-concerto
    +M:4/4
    +L:1/16
    +K:F
    +%%staves [(4 5 6) (13 14) 16]
    +V:4 nm="red-0" clef=treble
    +V:13 nm="purple-0" clef=treble
    +V:16 nm="brown-0" clef=treble
    +V:4
    +[K:F]z8[F2F,2][A2A,2]....
    +
    +
    +
    +

    +After modifying the .abc file we get the following: +

    + +
    +
    +X:1
    +T:Haendel - Concerto for Organ and Orchestra - Opus 4 nr 4
    +M:4/4
    +L:1/16
    +K:F
    +%%staves [(13 14) 16 (4 5 6)]
    +V:4 nm=Violini clef=treble
    +V:13 nm=Organo1 clef=treble
    +V:16 nm=Organo2 clef=bass
    +V:4
    +[K:F]z8[F2F,2][A2A,2]...
    +
    +
    +
    +

    +A script command single-voice is available, especially to extract separate +voices from a multi-voice score. Use this command to generate an .abc +file, then use this file with a customized header as input for the Abcm2ps tool. If you know how +to write shell scripts then it's possible to generate the scores for all musicians +of an orchestra with one single shell command. + + +

    Tr-sco - transform a score file

    +This command-line app is for modifying an existing score file. The duration of the notes +can be modified: halved, doubled, or quantitized. Usage: +
    +   tr-sco <option> <score-file>
    +
    +where <option> is: + + + + + + + + + + + +
    -trh + Notes are halved. +
    -trd + Notes are doubled. +
    -trq + Start- and end delays are omitted. +
    -trs n + Notes are shifted n semi-tones. Accidentals will be flat's. +
    -hi + With option -trs, accidentals will be sharp's. +
    +

    +The output file is always the same: tr.sco + + +

    Issues

    +

    Scared of the command line?

    +With some restrictions, Amuc can be run from the desktop, not using a terminal. +If you installed the app from a binary package (e.g. amuc_1.7-1_i386.deb for Debian or Ubuntu) +then a 'desktop file' is available: /usr/share/applications/amuc.desktop. +Using your File Manager you can drag its icon to the desktop or to the panel. +

    +A second option is to go with the File Manager to a directory where .sco and .src files +are located (demo files are in /usr/share/amuc/amuc-tunes). +These files have MIME-type text/plain. E.g. using
    nautilus --no-desktop .
    you can +make this type of file, with extension .scr or .sco, to be opened by Amuc. If nautilus is +already running, then you can click with the right mouse button on the file icon. +

    +It's not useful to run companion tools Wav2score and Abcm2ps this way, as they need +several command-line options to do their job. + + +

    Hardware requirements

    +Real-time sound generation needs a lot of +processing power. When too much voices are active at the same moment +the computer might be unable to perform all calculations in time, resulting in +gaps in the generated sound. However, this will happen only with an old, slow computer. +

    +The maximum number of voices that can sound at the same moment is 50. +If this number is exceeded then a warning is issued. Notice that a note +that is in its decaying phase still occupies a voice, so if short decay values +are choosen then more notes can play together. + + +

    The GUI

    +As you may have noticed, the graphical user interface is not very standard, +which may be a hurdle for new users. However, the app and its interface is intended for "power users". +Composing music is not an easy endeavour, and getting used to an uncommon GUI +should be the easy part of the job. +

    +If you want to do something that's not possible, then simply nothing happens. If you quit +the app after extensive modifications of the tunes without hitting the save butten, +then no warning whatsoever will stop you. + + +

    Musical quality

    +To be honest, it is clear that a lot more control of musical content +would be needed to create a really pleasant listening experience. In big +commercial apps like Cubase or Logic this control is available, but deploying it takes +much time, taste and planning. Listening to +a human jazz ensemble will make clear that this musical beauty never will be +reached artificially. Certain types of music however can sound really good, +listen for example to the organ concerto from the Amuc distribution. +

    +However, Amuc is especially fit for trying out musical ideas, and for experimenting +with different sounds and combinations thereoff. After automatic translation +towards regular music scores, a composed piece could be played by real musicians. +You will notice however that preparing a piece to be played by humans usually +involves a rewrite, because the timing of the notes must be simplified in order to avoid +many dotted notes, and also the instruments will have to be reallocated in order +to match the possibilities of real instruments. +

    +If you cannot read scores or you do not know the fundamentals of harmony then Amuc +probably is of little use for you. However, the art of composing is something that +can be learned. A very good and practical book about composing, especially jazz and pop: +

    +Jazz Composition and Arranging
    +Author: Tom Boras
    +Publisher: Thomson Schirmer
    +ISBN 0-534-25261-3 +
    +Also on-line several tutorials can be found. A great source of knowledge is +at www.dolmetsch.com. +

    +


    + + diff --git a/doc/amuc-title-transp.png b/doc/amuc-title-transp.png new file mode 100644 index 0000000..02f7239 Binary files /dev/null and b/doc/amuc-title-transp.png differ diff --git a/doc/amuc-title.gif b/doc/amuc-title.gif new file mode 100644 index 0000000..33ed206 Binary files /dev/null and b/doc/amuc-title.gif differ diff --git a/doc/amuc-title.png b/doc/amuc-title.png new file mode 100644 index 0000000..4c3c966 Binary files /dev/null and b/doc/amuc-title.png differ diff --git a/doc/amuc.1 b/doc/amuc.1 new file mode 100644 index 0000000..8239984 --- /dev/null +++ b/doc/amuc.1 @@ -0,0 +1,58 @@ +.TH "AMUC" "1" "Sept 1 2008" "amuc-1.7" "AMUC Manual Pages" +.SH +NAME +.PP +\fBamuc\fP - the Amsterdam Music Composer +.SH +SYNOPSIS +.PP +amuc [\fIOPTIONS\fP] [\fIFILE\fP] +.SH +DESCRIPTION +.PP +\fBamuc\fP is a light-weight Linux application for composing and playing music. +.br +It works like this: +.br +Tune fragments are entered graphically, combined into a complete +tune by means of a script file. Several instruments, mono-synthesizers +and percussive sounds are provided, as well as sampled sounds. +Tunes can be exported as WAV or MIDI files, or as human-readable scores +in postscript format. MIDI files can be imported. +.br +For the inexperienced composer there is help available with scales +and chords. +.SH +FILE +.PP +The file should be a 'score file' (extension: .sco) or a 'script file' +(extension: .scr). If a script file is read, also a score file with the +same base name should be present. A script file is a text file created by the user, +a score file is generated by Amuc. +.SH +OPTIONS +.TP +\fB-h\fP +.br +Print usage message and exit. +.TP +\fB-db\fP +.br +Print debug information at run time. +.TP +\fB-mm\fP +.br +Print MIDI messages during MIDI import. +.TP +\fB-nt\fP +.br +Startup without default tunes. +.TP +\fB-V\fP +.br +Print version and exit. +.SH +CONFIGURATION FILE +.PP +If Amuc is started for the first time, then a configuration file .amucrc is written to the +home directory. This file should be self-explanationary. diff --git a/doc/amuc.png b/doc/amuc.png new file mode 100644 index 0000000..2e1c937 Binary files /dev/null and b/doc/amuc.png differ diff --git a/doc/chords-and-scales.html b/doc/chords-and-scales.html new file mode 100644 index 0000000..11b53d4 --- /dev/null +++ b/doc/chords-and-scales.html @@ -0,0 +1,64 @@ + +Chords and scales + +In jazz, the association +between many common chords and useful scales has been summarised in the +table below which is taken from the excellent Jazz Primer by Marc Sabatella. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    chord(s)scale(s)
    Cmaj7, Cmaj9, C6, CC major, C lydian, C major bebop, C +major pentatonic, G major pentatonic
    Cmaj7#11C lydian, B in sen
    Cm7, Cm9, Cm11, CmC dorian, C minor bebop, C minor +pentatonic, F major pentatonic, Bb major pentatonic, Eb major bebop, C +blues, C minor
    Cm6, CmC dorian, C melodic minor, C minor pentatonic, +F major pentatonic, Bb major pentatonic, C minor bebop, Eb major bebop, +D in sen
    Cm-maj7C melodic minor, C harmonic +minor, Eb major bebop
    Cm7b6C minor, Ab major pentatonic
    Cm7b9C phrygian, C phrygian #6
    C7, C9, C13, CC mixolydian, C lydian dominant, C +dominant bebop, C blues, C major pentatonic
    C7sus, Csus, C11, Bb/C, Gm7/CC mixolydian, C suspended +pentatonic, F major pentatonic
    C7#11, C7C lydian dominant
    C7alt, C7#9#5, C7#9C altered, F harmonic minor, F +melodic minor
    C7b9b5, C7b9C HW diminished, F harmonic minor, F +melodic minor
    C7aug, C7+, C7#5C whole tone
    Cm7b5C locrian #2, C locrian
    Cdim7C WH diminished
    CphrygC phrygian, C phrygian #6, C Spanish phrygian, C +in sen
    Cmaj7#5C lydian augmented, C major bebop
    C7susb9C phrygian #6, C phrygian
    + + diff --git a/doc/edit-window.png b/doc/edit-window.png new file mode 100644 index 0000000..c4ab489 Binary files /dev/null and b/doc/edit-window.png differ diff --git a/doc/file-menu.png b/doc/file-menu.png new file mode 100644 index 0000000..5f2dea5 Binary files /dev/null and b/doc/file-menu.png differ diff --git a/doc/icon2.png b/doc/icon2.png new file mode 100644 index 0000000..88c948c Binary files /dev/null and b/doc/icon2.png differ diff --git a/doc/keys-window.png b/doc/keys-window.png new file mode 100644 index 0000000..34e30e8 Binary files /dev/null and b/doc/keys-window.png differ diff --git a/doc/mail-adr.gif b/doc/mail-adr.gif new file mode 100644 index 0000000..e751cab Binary files /dev/null and b/doc/mail-adr.gif differ diff --git a/doc/mail-adr.png b/doc/mail-adr.png new file mode 100644 index 0000000..6443be5 Binary files /dev/null and b/doc/mail-adr.png differ diff --git a/doc/mono-synth.png b/doc/mono-synth.png new file mode 100644 index 0000000..8410b73 Binary files /dev/null and b/doc/mono-synth.png differ diff --git a/doc/no-zoom.png b/doc/no-zoom.png new file mode 100644 index 0000000..705632d Binary files /dev/null and b/doc/no-zoom.png differ diff --git a/doc/org-conc1.png b/doc/org-conc1.png new file mode 100644 index 0000000..a462704 Binary files /dev/null and b/doc/org-conc1.png differ diff --git a/doc/org-conc2.png b/doc/org-conc2.png new file mode 100644 index 0000000..75cced0 Binary files /dev/null and b/doc/org-conc2.png differ diff --git a/doc/org_c3.png b/doc/org_c3.png new file mode 100644 index 0000000..ea190a1 Binary files /dev/null and b/doc/org_c3.png differ diff --git a/doc/piano-mode.png b/doc/piano-mode.png new file mode 100644 index 0000000..000efd4 Binary files /dev/null and b/doc/piano-mode.png differ diff --git a/doc/ps-notes.png b/doc/ps-notes.png new file mode 100644 index 0000000..4cc2f01 Binary files /dev/null and b/doc/ps-notes.png differ diff --git a/doc/sampled-phys.png b/doc/sampled-phys.png new file mode 100644 index 0000000..b29b308 Binary files /dev/null and b/doc/sampled-phys.png differ diff --git a/doc/sampled-wave.png b/doc/sampled-wave.png new file mode 100644 index 0000000..9841929 Binary files /dev/null and b/doc/sampled-wave.png differ diff --git a/doc/score-display.png b/doc/score-display.png new file mode 100644 index 0000000..3747d7a Binary files /dev/null and b/doc/score-display.png differ diff --git a/doc/wav2score.png b/doc/wav2score.png new file mode 100644 index 0000000..36a1940 Binary files /dev/null and b/doc/wav2score.png differ diff --git a/doc/zoomed.png b/doc/zoomed.png new file mode 100644 index 0000000..8cf9ac8 Binary files /dev/null and b/doc/zoomed.png differ diff --git a/samples/00-Impact.wav b/samples/00-Impact.wav new file mode 100644 index 0000000..0864411 Binary files /dev/null and b/samples/00-Impact.wav differ diff --git a/samples/01-LowFoot.wav b/samples/01-LowFoot.wav new file mode 100644 index 0000000..62b740e Binary files /dev/null and b/samples/01-LowFoot.wav differ diff --git a/samples/02-Foot.wav b/samples/02-Foot.wav new file mode 100644 index 0000000..bfccc53 Binary files /dev/null and b/samples/02-Foot.wav differ diff --git a/samples/03-FlangeKick.wav b/samples/03-FlangeKick.wav new file mode 100644 index 0000000..7f2b15f Binary files /dev/null and b/samples/03-FlangeKick.wav differ diff --git a/samples/05-LowLowTom.wav b/samples/05-LowLowTom.wav new file mode 100644 index 0000000..2acf539 Binary files /dev/null and b/samples/05-LowLowTom.wav differ diff --git a/samples/06-LowTom.wav b/samples/06-LowTom.wav new file mode 100644 index 0000000..1fdda71 Binary files /dev/null and b/samples/06-LowTom.wav differ diff --git a/samples/07-Tom.wav b/samples/07-Tom.wav new file mode 100644 index 0000000..47462e0 Binary files /dev/null and b/samples/07-Tom.wav differ diff --git a/samples/08-HiTom.wav b/samples/08-HiTom.wav new file mode 100644 index 0000000..1b62e97 Binary files /dev/null and b/samples/08-HiTom.wav differ diff --git a/samples/09-HiHiTom.wav b/samples/09-HiHiTom.wav new file mode 100644 index 0000000..6cf2590 Binary files /dev/null and b/samples/09-HiHiTom.wav differ diff --git a/samples/10-LowSnare.wav b/samples/10-LowSnare.wav new file mode 100644 index 0000000..fcdee72 Binary files /dev/null and b/samples/10-LowSnare.wav differ diff --git a/samples/11-Snare.wav b/samples/11-Snare.wav new file mode 100644 index 0000000..d451d7a Binary files /dev/null and b/samples/11-Snare.wav differ diff --git a/samples/12-HiSnare.wav b/samples/12-HiSnare.wav new file mode 100644 index 0000000..21a2289 Binary files /dev/null and b/samples/12-HiSnare.wav differ diff --git a/samples/15-FringeSnare.wav b/samples/15-FringeSnare.wav new file mode 100644 index 0000000..8b86e1f Binary files /dev/null and b/samples/15-FringeSnare.wav differ diff --git a/samples/16-TrashySnare.wav b/samples/16-TrashySnare.wav new file mode 100644 index 0000000..d283da8 Binary files /dev/null and b/samples/16-TrashySnare.wav differ diff --git a/samples/20-LowSidestick.wav b/samples/20-LowSidestick.wav new file mode 100644 index 0000000..7eb6aa4 Binary files /dev/null and b/samples/20-LowSidestick.wav differ diff --git a/samples/21-Sidestick.wav b/samples/21-Sidestick.wav new file mode 100644 index 0000000..1bdcb40 Binary files /dev/null and b/samples/21-Sidestick.wav differ diff --git a/samples/22-HiSidestick.wav b/samples/22-HiSidestick.wav new file mode 100644 index 0000000..6c7e602 Binary files /dev/null and b/samples/22-HiSidestick.wav differ diff --git a/samples/30-Crash.wav b/samples/30-Crash.wav new file mode 100644 index 0000000..6d554b1 Binary files /dev/null and b/samples/30-Crash.wav differ diff --git a/samples/31-Ride.wav b/samples/31-Ride.wav new file mode 100644 index 0000000..495d7cb Binary files /dev/null and b/samples/31-Ride.wav differ diff --git a/samples/32-RideBell.wav b/samples/32-RideBell.wav new file mode 100644 index 0000000..9ed6841 Binary files /dev/null and b/samples/32-RideBell.wav differ diff --git a/samples/33-OpenHihat.wav b/samples/33-OpenHihat.wav new file mode 100644 index 0000000..1f67fb7 Binary files /dev/null and b/samples/33-OpenHihat.wav differ diff --git a/samples/35-LongClosedHihat.wav b/samples/35-LongClosedHihat.wav new file mode 100644 index 0000000..388d20a Binary files /dev/null and b/samples/35-LongClosedHihat.wav differ diff --git a/samples/36-ClosedHihat.wav b/samples/36-ClosedHihat.wav new file mode 100644 index 0000000..d6af579 Binary files /dev/null and b/samples/36-ClosedHihat.wav differ diff --git a/samples/37-ShortClosedHihat.wav b/samples/37-ShortClosedHihat.wav new file mode 100644 index 0000000..514f6f6 Binary files /dev/null and b/samples/37-ShortClosedHihat.wav differ diff --git a/samples/40-LowLowLowConga.wav b/samples/40-LowLowLowConga.wav new file mode 100644 index 0000000..2a09cfe Binary files /dev/null and b/samples/40-LowLowLowConga.wav differ diff --git a/samples/41-LowLowConga.wav b/samples/41-LowLowConga.wav new file mode 100644 index 0000000..38c61ec Binary files /dev/null and b/samples/41-LowLowConga.wav differ diff --git a/samples/42-LowConga.wav b/samples/42-LowConga.wav new file mode 100644 index 0000000..22dd3bf Binary files /dev/null and b/samples/42-LowConga.wav differ diff --git a/samples/43-MidConga.wav b/samples/43-MidConga.wav new file mode 100644 index 0000000..bc47a21 Binary files /dev/null and b/samples/43-MidConga.wav differ diff --git a/samples/44-HiConga.wav b/samples/44-HiConga.wav new file mode 100644 index 0000000..7be1c45 Binary files /dev/null and b/samples/44-HiConga.wav differ diff --git a/samples/45-HiHiConga.wav b/samples/45-HiHiConga.wav new file mode 100644 index 0000000..118b9bb Binary files /dev/null and b/samples/45-HiHiConga.wav differ diff --git a/samples/50-Triangle.wav b/samples/50-Triangle.wav new file mode 100644 index 0000000..ef86bb2 Binary files /dev/null and b/samples/50-Triangle.wav differ diff --git a/samples/51-Cabasa.wav b/samples/51-Cabasa.wav new file mode 100644 index 0000000..2aa9bb3 Binary files /dev/null and b/samples/51-Cabasa.wav differ diff --git a/samples/52-Rimshot.wav b/samples/52-Rimshot.wav new file mode 100644 index 0000000..834d631 Binary files /dev/null and b/samples/52-Rimshot.wav differ diff --git a/samples/53-Timbale.wav b/samples/53-Timbale.wav new file mode 100644 index 0000000..b1aa852 Binary files /dev/null and b/samples/53-Timbale.wav differ diff --git a/samples/54-Clap.wav b/samples/54-Clap.wav new file mode 100644 index 0000000..60f9725 Binary files /dev/null and b/samples/54-Clap.wav differ diff --git a/samples/55-Cowbell.wav b/samples/55-Cowbell.wav new file mode 100644 index 0000000..c912ce7 Binary files /dev/null and b/samples/55-Cowbell.wav differ diff --git a/src-abcm2ps/Makefile b/src-abcm2ps/Makefile new file mode 100644 index 0000000..ba7d185 --- /dev/null +++ b/src-abcm2ps/Makefile @@ -0,0 +1,21 @@ +CC = g++ +OBJECTS=abcparse.o buffer.o deco.o draw.o format.o music.o parse.o subs.o syms.o +# Using environment variable DBG, which might be: "-g -Wuninitialized -Wall -Wno-non-virtual-dtor" + +all: abc2ps.a abcm2ps + +abc2ps.a: abcm2ps.o $(OBJECTS) + ar rs abc2ps.a abcm2ps.o $(OBJECTS) + +abcm2ps: main.o $(OBJECTS) + $(CC) -o abcm2ps main.o $(OBJECTS) + +.SUFFIXES= + +%.o: %.cpp + $(CC) -c -O $(DBG) -Wno-char-subscripts -Wno-sign-compare $< + +main.o: abcm2ps.cpp + $(CC) -c -DREAD_FILE -o main.o $< + +$(OBJECTS) main.o abcm2ps.o: abcparse.h abc2ps.h diff --git a/src-abcm2ps/abc2ps.h b/src-abcm2ps/abc2ps.h new file mode 100644 index 0000000..3e697eb --- /dev/null +++ b/src-abcm2ps/abc2ps.h @@ -0,0 +1,463 @@ +/* -- general macros -- */ +#define VERBOSE0 2 /* default verbosity */ +#define OUTPUTFILE "Out.ps" /* standard output file */ +#if defined(unix) || defined(__unix__) +#define DIRSEP '/' +#else +#define DIRSEP '\\' +#endif + +/* basic page dimensions */ +#ifdef A4_FORMAT +#define PAGEHEIGHT (29.7 * CM) +#define PAGEWIDTH (21.0 * CM) +#else +#define PAGEHEIGHT (11.0 * IN) +#define PAGEWIDTH (8.5 * IN) +#endif + +#define PS_LEVEL 2 +#define DEFAULT_FDIR "/usr/share/abcm2ps" +/* -- macros controlling music typesetting -- */ + +#define STEM_YOFF 1.0 /* offset stem from note center */ +#define STEM_XOFF 3.5 +#define STEM 20 /* standard stem length */ +#define STEM_MIN 16 /* min stem length under beams */ +#define STEM_MIN2 12 /* ... for notes with two beams */ +#define STEM_MIN3 10 /* ... for notes with three beams */ +#define STEM_MIN4 10 /* ... for notes with four beams */ +#define STEM_CH 16 /* standard stem length for chord */ +#define STEM_CH_MIN 12 /* min stem length for chords under beams */ +#define STEM_CH_MIN2 8 /* ... for notes with two beams */ +#define STEM_CH_MIN3 7 /* ... for notes with three beams */ +#define STEM_CH_MIN4 7 /* ... for notes with four beams */ +#define BEAM_DEPTH 3.2 /* width of a beam stroke (was 2.6) */ +#define BEAM_OFFSET 0.25 /* pos of flat beam relative to staff line */ +#define BEAM_SHIFT 5.0 /* shift of second and third beams (was 5.3) */ +/* To align the 4th beam as the 1st: shift=6-(depth-2*offset)/3 */ +#define BEAM_FLATFAC 0.6 /* factor to decrease slope of long beams */ +#define BEAM_THRESH 0.06 /* flat beam if slope below this threshold */ +#define BEAM_SLOPE 0.5 /* max slope of a beam */ +#define BEAM_STUB 6.0 /* length of stub for flag under beam */ +#define SLUR_SLOPE 1.0 /* max slope of a slur */ +#define DOTSHIFT 5 /* shift dot when up flag on note */ +#define GSTEM 13.0 /* grace note stem length */ +#define GSTEM_XOFF 2.0 /* x offset for grace note stem */ +#define GSPACE0 12.0 /* space from grace note to big note */ +#define GSPACE 8.0 /* space between grace notes */ +#define CUT_NPLETS 0 /* 1 to always cut nplets off beams */ +#define RANFAC 0.05 /* max random shift = RANFAC * spacing */ + +#define BETA_C 0.1 /* max expansion for flag -c */ +#define ALFA_X 1.0 /* max compression before complaining */ +#define BETA_X 1.2 /* max expansion before complaining */ + +#define VOCPRE 0.4 /* portion of vocals word before note */ +#define GCHPRE 0.4 /* portion of guitar chord before note */ +#define LYDIG_SH 3. /* shift for lyrics starting with a digit */ + +/* -- Parameters for note spacing -- */ +/* -- bnn determines how strongly the first note enters into the spacing. + For bnn=1, the spacing is calculated using the first note. + For bnn=0, the spacing is the average for the two notes. + -- fnn multiplies the spacing under a beam, to compress the notes a bit + -- gnn multiplies the spacing a second time within an n-tuplet + */ + +#define bnnp 0.9 +#define fnnp 0.9 +#define gnnp 0.8 + +/* -- macros for program internals -- */ + +#define CM 28.35 /* factor to transform cm to pt */ +#define PT 1.00 /* factor to transform pt to pt */ +#define IN 72.00 /* factor to transform inch to pt */ + +#define STRL1 128 /* string length for file names */ +#define MAXSTAFF 16 /* max staves */ +#define BSIZE 512 /* buffer size for one input string */ +#define BUFFSZ 100000 /* size of output buffer */ +#define BUFFSZ1 5000 /* buffer reserved for one staff */ + +#define BREVE (BASE_LEN * 2) /* double note (square note) */ +#define SEMIBREVE BASE_LEN /* whole note */ +#define MINIM (BASE_LEN / 2) /* half note (white note) */ +#define CROTCHET (BASE_LEN / 4) /* quarter note (black note) */ +#define QUAVER (BASE_LEN / 8) /* 1/8 note */ +#define SEMIQUAVER (BASE_LEN / 16) /* 1/16 note */ + +#define SWFAC 0.50 /* factor to estimate width of string */ + +#define MAXFORMATS 10 /* max number of defined page formats */ +#define STRLFMT 81 /* string length in FORMAT struct */ +#define MAXFONTS 20 /* max number of fonts */ +#define MAXENC 6 /* max number of ISO-Latin encodings */ + +#define OBEYLINES 0 +#define T_JUSTIFY 1 +#define T_FILL 2 +#define OBEYCENTER 3 +#define SKIP 4 + +#define NCOMP 5 /* max number of composer lines */ +#define NTITLE 3 /* max number of title lines */ + +typedef unsigned char uchar; + +struct ISTRUCT { /* information fields */ + const char *area, + *book, + *comp[NCOMP], + *orig, + *parts, + *rhyth, + *src; + struct SYMBOL *tempo; + const char *title[NTITLE]; + const char *xref; + short ncomp; + short ntitle; +}; + +extern struct ISTRUCT info, default_info; + +extern char deco_glob[128], deco_tune[128]; +//extern char deco_glob[256], deco_tune[256]; + +/* lyrics */ +#define MAXLY 16 /* max number of lyrics */ +struct lyrics { + char *w[MAXLY]; /* ptr to words */ +}; + +/* lyric fonts */ +struct lyric_fonts_s { + int font; + float size; +}; +extern struct lyric_fonts_s lyric_fonts[8]; +extern int nlyric_font; + +enum { + NO_TYPE, /* invalid type */ + NOTE, /* valid symbol types */ + REST, + BAR, + CLEF, + TIMESIG , + KEYSIG, + TEMPO, + STAVES, + MREST, + PART, + MREP, + GRACE, + FMTCHG +}; + +const int + S_EOLN= 0x0001, /* end of line */ + S_WORD_ST= 0x0002, /* word starts here */ + S_BEAM_BREAK= 0x0004, /* 2nd beam must restart here */ + S_NO_HEAD= 0x0008, /* don't draw note head */ + S_2S_BEAM= 0x0010, /* beam on 2 staves */ + S_NPLET_ST= 0x0020, /* start or in a n-plet sequence */ + S_NPLET_END= 0x0040, /* end or in a n-plet sequence */ + S_RRBAR= 0x0080; /* right repeat bar */ + +enum { + H_FULL, + H_EMPTY, + H_OVAL, + H_SQUARE +}; +enum { + STBRK, /* staff break */ + PSSEQ /* postscript sequence */ +}; +/* music element */ +struct SYMBOL:abcsym { /* struct for a drawable symbol */ + struct SYMBOL *nxt, *prv; /* voice linkage */ + char type; /* symbol type */ + char seq; /* sequence # - see parse.c */ + char voice; /* voice (0..nvoice) */ + unsigned char staff; /* staff (0..nstaff) */ + int len; /* main note length */ + signed char pits[MAXHD]; /* pitches for notes */ + struct SYMBOL *ts_next, *ts_prev; /* time linkage */ + int time; /* starting time */ + char sflags; /* symbol flags */ + char nhd; /* number of notes in chord - 1 */ + signed char stem; /* 1 / -1 for stem up / down */ + signed char nflags; /* number of note flags when > 0 */ + char dots; /* number of dots */ + char head; /* head type */ + signed char multi; /* multi voice in the staff (+1, 0, -1) */ + short u; /* auxillary information: + * - small clef when CLE + * - old key signature when KEYSIG + * - reset bar number when BAR + * - subtype when FMTCHG (format change) + * with value in xmx: */ + short doty; /* dot y pos when voices overlap */ + float x; /* position */ + short y; + short ymn, ymx, yav; /* min,max,avg note head height */ + float xmx; /* max h-pos of a head rel to top */ + float dc_top; /* max offset needed for decorations */ + float dc_bot; /* min offset for decoration */ + float xs, ys; /* position of stem end */ + float wl, wr; /* left,right min width */ + float pl, pr; /* left,right preferred width */ + float shrink, stretch; /* glue before this symbol */ + float shhd[MAXHD]; /* horizontal shift for heads */ + float shac[MAXHD]; /* horizontal shift for accidentals */ + struct lyrics *ly; /* lyrics */ + struct SYMBOL *grace; /* grace notes */ +}; + +/* bar types */ +const int + B_INVIS=B_OBRA, /* invisible; for endings without bars */ + B_SINGLE=B_BAR, /* | single bar */ + B_DOUBLE=0x11, /* || thin double bar */ + B_THIN_THICK=0x13, /* |] thick at section end */ + B_THICK_THIN=0x21, /* [| thick at section start */ + B_LREP=0x14, /* |: left repeat bar */ + B_RREP=0x41, /* :| right repeat bar */ + B_DREP=0x44, /* :: double repeat bar */ + B_DASH=0x04; /* : dashed bar */ + +struct FONTSPEC { + int fnum; /* index to fontnames in format.c */ + float size; + float swfac; +}; + +struct FORMAT { /* struct for page layout */ + float pageheight, pagewidth; + float topmargin, botmargin, leftmargin, rightmargin; + float topspace, wordsspace, titlespace, subtitlespace, partsspace; + float composerspace, musicspace, staffsep, vocalspace, textspace; + float scale, maxshrink, lineskipfac, parskipfac, sysstaffsep; + float indent, infospace, slurheight, notespacingfactor; + int landscape, titleleft, continueall, writehistory; + int stretchstaff, stretchlast, withxrefs, barsperstaff; + int oneperpage, musiconly, titlecaps, graceslurs, straightflags; + int splittune, encoding, partsbox, infoline, printtempo, autoclef; + int measurenb, measurefirst, measurebox, flatbeams, squarebreve; + int exprabove, exprbelow, breathlow, vocalabove, freegchord; + int printparts, gchordbox; + const char *footer, *header; + struct FONTSPEC titlefont, subtitlefont, vocalfont, textfont; + struct FONTSPEC tempofont, composerfont, partsfont, gchordfont; + struct FONTSPEC wordsfont, footerfont, headerfont, infofont; + struct FONTSPEC repeatfont, measurefont; +}; + +extern struct FORMAT cfmt; /* current format for output */ + +extern char *mbf; /* where to PUTx() */ +extern int nbuf; /* number of bytes buffered */ +extern int use_buffer; /* 1 if lines are being accumulated */ + +extern char page_init[201]; /* initialization string after page break */ +extern int tunenum; /* number of current tune */ +extern int pagenum; /* current page number */ +extern int nbar; /* current measure number */ +extern int nbar_rep; /* last repeat bar number */ + +extern int in_page; + + /* switches modified by flags: */ +extern int pagenumbers; /* write page numbers ? */ +extern int epsf; /* for EPSF postscript output */ +extern int choose_outname; /* 1 names outfile w. title/fnam */ +extern int break_continues; /* ignore continuations ? */ + +//extern char outf[STRL1]; /* output file name */ +extern const char *in_fname; /* current input file name */ + +extern int file_initialized; /* for output file */ +extern FILE *fout; /* output file */ + +#define MAXWHISTLE 4 /* max number of whistle tablature */ +struct WHISTLE_S { + short voice; /* voice number */ + short pitch; /* absolute key pitch */ +}; +extern struct WHISTLE_S whistle_tb[MAXWHISTLE]; +extern int nwhistle; + +extern int s_argc; /* command line arguments */ +extern char **s_argv; + +struct STAFF { + struct clef_s clef; /* base clef */ + unsigned brace:1; /* 1st staff of a brace */ + unsigned brace_end:1; /* 2nd staff of a brace */ + unsigned bracket:1; /* 1st staff of a bracket */ + unsigned bracket_end:1; /* last staff of a bracket */ + unsigned forced_clef:1; /* explicit clef */ + unsigned stop_bar:1; /* stop drawing bar on this staff */ + float y; /* y position */ + short nvocal; /* number of vocals (0..n) */ +}; +extern struct STAFF staff_tb[MAXSTAFF]; +extern int nstaff; /* (0..MAXSTAFF-1) */ + +struct VOICE_S { + struct SYMBOL *sym; /* associated symbols */ + struct SYMBOL *last_symbol; /* last symbol while scanning */ + struct SYMBOL *s_anc; /* ancillary symbol pointer */ + struct VOICE_S *next, *prev; /* staff links */ + const char *name; /* voice id */ + char *nm; /* voice name */ + char *snm; /* voice subname */ + struct clef_s clef; /* current clef */ + struct key_s key; /* current key signature */ + struct meter_s meter; /* current time signature */ + float yvocal; /* current vocal vertical offset */ + char *bar_text; /* bar text at start of staff when bar_start */ + int time; /* current time while parsing */ + unsigned forced_clef:1; /* explicit clef */ + unsigned second:1; /* secondary voice in a brace/parenthesis */ + unsigned floating:1; /* floating voice in a brace */ + unsigned selected:1; /* selected while sorting by time (music.c) */ + unsigned bar_repeat:1; /* bar at start of staff is a repeat bar */ + signed char bar_start; /* bar type at start of staff / -1 */ + char r_plet; /* number of n-plet notes while parsing */ + char nvocal; /* number of vocals (0..n) */ + signed char clone; /* duplicate from this voice number */ + unsigned char staff; /* staff (0..n-1) */ + signed char sfp; /* key signature while parsing */ + signed char stem; /* stem direction while parsing */ +}; +extern struct VOICE_S voice_tb[MAXVOICE]; /* voice table */ +extern int nvoice; /* (0..MAXVOICE-1) */ +extern int current_voice; /* current voice while parsing */ +extern struct VOICE_S *first_voice; /* first_voice */ + +extern float realwidth; /* real staff width while generating */ + +const int SPACETB_SZ=9; +extern float space_tb[SPACETB_SZ], dot_space; /* note spacing */ + +/* PUTn: add to buffer with n arguments */ +#define PUT0(f) do {strcpy(mbf,f); a2b(); } while (0) +#define PUT1(f,a) do {sprintf(mbf,f,a); a2b(); } while (0) +#define PUT2(f,a,b) do {sprintf(mbf,f,a,b); a2b(); } while (0) +#define PUT3(f,a,b,c) do {sprintf(mbf,f,a,b,c); a2b(); } while (0) +#define PUT4(f,a,b,c,d) do {sprintf(mbf,f,a,b,c,d); a2b(); } while (0) +#define PUT5(f,a,b,c,d,e) do {sprintf(mbf,f,a,b,c,d,e); a2b(); } while (0) + +/* -- external routines -- */ +/* abc2ps.c */ +void ops_into_fmt(); +void strext(char *fid, + char *ext); +/* buffer.c */ +void a2b(); +void abskip(float h); +void buffer_eob(); +void bskip(float h); +void check_buffer(); +void clear_buffer(); +void close_output_file(bool report); +float get_bposy(); +void write_buffer(); +void open_output_file(const char *out_file); +void set_buffer(float *p_v); +void write_eps(); +void write_pagebreak(); +/* deco.c */ +void deco_add(char *text); +void deco_cnv(struct deco *dc, struct SYMBOL *s); +char deco_intern(unsigned char deco); +void deco_update(struct SYMBOL *s, float dx); +float deco_width(struct SYMBOL *s); +void draw_all_deco(); +void draw_deco_near(); +void draw_deco_note(); +void draw_deco_staff(); +void reset_deco(); +float draw_partempo(float top, + int any_part, + int any_tempo, + int any_vocal); +/* draw.c */ +void draw_staff(int mline, + float indent); +void draw_sym_near(); +void draw_symbols(struct VOICE_S *p_voice); +void draw_whistle(); +/* format.c */ +int interpret_format_line(char *w, + char *p); +void define_fonts(); +void make_font_list(); +void print_format(); +int read_fmt_file(const char *filename, + const char *dirname); +void set_format(); +/* music.c */ +void output_music(); +void reset_gen(); +/* parse.c */ +extern float multicol_start; +struct SYMBOL *add_sym(struct VOICE_S *p_voice, + int type); +void voice_dup(); +void do_tune(struct abctune *t, + int header_only); +void identify_note(struct SYMBOL *s, + int len, + int *p_head, + int *p_dots, + int *p_flags); +struct SYMBOL *ins_sym(int type, + struct SYMBOL *s); +/* subs.c */ +void bug(const char *msg, + int fatal); +void cap_str(char *c); +float ranf(float x1, + float x2); +float scan_u(char *str); +/* types of a text line */ +enum { + TEXT_H, /* H: */ + TEXT_W, /* W: */ + TEXT_Z, /* Z: */ + TEXT_N, /* N: */ + TEXT_D, /* D: */ + TEXT_PS, /* postscript format */ + TEXT_MAX +}; +void add_text(const char *str, + int type); +void add_to_text_block(char *s, + int job); +void clear_text(); +float cwid(char c); +int is_xrefstr(char *str); +void put_history(); +void put_words(); +void set_font(struct FONTSPEC *font); +void tex_str(char *d, + const char *s, + int maxlen, + float *wid); +void write_title(int i); +void write_heading(); +void write_user_ps(); +void write_text_block(int job, + int abc_state); +/* syms.c */ +void define_encoding(int enc); +void define_font(char *name, + int num); +void define_symbols(); +void alert(const char *form,...); diff --git a/src-abcm2ps/abcm2ps.cpp b/src-abcm2ps/abcm2ps.cpp new file mode 100644 index 0000000..3f0b189 --- /dev/null +++ b/src-abcm2ps/abcm2ps.cpp @@ -0,0 +1,310 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ +#include +#include +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +/* -- global variables -- */ + +struct ISTRUCT info, default_info; +char deco_glob[128], deco_tune[128]; +struct SYMBOL *sym; /* (points to the symbols of the current voice) */ + +char page_init[201]; /* initialization string after page break */ +//char outf[STRL1]; /* output file name */ +int tunenum; /* number of current tune */ +int pagenum = 1; /* current page in output file */ + +int in_page; + +/* switches modified by flags: */ +int pagenumbers; /* write page numbers ? */ +int epsf; /* for EPSF postscript output */ +int choose_outname; /* 1 names outfile w. title/fnam */ + +int file_initialized; /* for output file */ +FILE *fout; /* output file */ + +int s_argc; /* command line arguments */ +char **s_argv; + +struct WHISTLE_S whistle_tb[MAXWHISTLE]; +int nwhistle; + +/* -- local variables -- */ + +static int include_xrefs = -1; /* to include xref numbers in title */ +static int one_per_page = -1; /* new page for each tune */ +static int splittune = -1; /* tune may be splitted */ +static int write_history = -1; /* write history and notes */ +static float alfa_c = -1.0; /* max compression allowed */ +static int bars_per_line = -1; /* bars for auto linebreaking */ +static int encoding = -1; /* latin encoding number */ +static int continue_lines = 1; /* flag to continue all lines */ +static int landscape = -1; /* flag for landscape output */ +static float lmargin = -1.0; /* left margin */ +static float indent = -1.0; /* 1st line indentation */ +static int music_only = -1; /* no vocals if 1 */ +static int flatbeams = -1; /* flat beams when bagpipe */ +static int graceslurs = -1; /* slurs in grace notes */ +static float scalefac = -1.0; /* scale factor for symbol size */ +static float staffsep = -1.0; /* staff separation */ +static const char *styd = DEFAULT_FDIR; /* format search directory */ +static int def_fmt_done = 0; /* default format read */ +static float swidth = -1.0; /* staff width */ +static int measurenb = 0; /* measure numbering (-1: none, 0: on the left, or every n bars) */ +static int measurebox = 0; /* display measure numbers in a box */ +static int measurefirst = -1; /* first measure number */ +static int printtempo = -1; /* print the tempo indications */ +const char *in_fname; /* current input file name */ + +/* -- local functions -- */ +static void read_def_format(const char*); +#ifdef READ_FILE +static void output_file(void); +static char *read_file(void); + +/* -- return the file extension -- */ +static char *getext(const char *fid) { + char *p; + + if ((p = (char*)strrchr(fid, DIRSEP)) == 0) + p = (char*)fid; + if ((p = strrchr(p, '.')) != 0) + return p + 1; + return 0; +} + +/* -- read an input file -- */ +static char *read_file(void) { + int fsize; + FILE *fin; + char *file; + + if ((fin = fopen(in_fname, "rb")) == 0) { + return 0; + } + if (fseek(fin, 0L, SEEK_END) < 0) { + fclose(fin); + return 0; + } + fsize = ftell(fin); + rewind(fin); + if ((file = (char*)malloc(fsize + 2)) == 0) { + fclose(fin); + return 0; + } + + if (fread(file, 1, fsize, fin) != (unsigned int)fsize) { + fclose(fin); + free(file); + return 0; + } + file[fsize] = '\0'; + fclose(fin); + return file; +} +#endif +/* -- set_page_format --- */ +static void set_page_format(const char *def_fmt) +{ + if (def_fmt) read_def_format(def_fmt); + ops_into_fmt(); + make_font_list(); +} + +/* -- read the default format -- */ +static void read_def_format(const char *def_fmt) +{ + if (!def_fmt || def_fmt_done) + return; + def_fmt_done = 1; + if (read_fmt_file(def_fmt, styd) < 0) + alert("Format file %s not found - using defaults",def_fmt); +} + +void ops_into_fmt(void) +{ + struct FORMAT *fmt; + + fmt = &cfmt; + if (landscape >= 0) + fmt->landscape = landscape; + if (scalefac > 0) + fmt->scale = scalefac; + if (lmargin >= 0) + fmt->leftmargin = lmargin; + if (indent >= 0) + fmt->indent = indent; + if (swidth >= 0) { + fmt->rightmargin = fmt->pagewidth - swidth - fmt->leftmargin; + if (fmt->rightmargin < 0) + alert("Warning: staffwidth too big"); + } + if (continue_lines >= 0) + fmt->continueall = continue_lines; + if (write_history >= 0) + fmt->writehistory = write_history; + if (bars_per_line >= 0) + fmt->barsperstaff = bars_per_line; + if (encoding >= 0) + fmt->encoding = encoding; + if (include_xrefs >= 0) + fmt->withxrefs = include_xrefs; + if (staffsep >= 0) + fmt->staffsep = staffsep; + if (one_per_page >= 0) + fmt->oneperpage = one_per_page; + if (splittune >= 0) + fmt->splittune = splittune; + if (music_only >= 0) + fmt->musiconly = music_only; + if (graceslurs >= 0) + fmt->graceslurs = graceslurs; + if (flatbeams >= 0) + fmt->flatbeams = flatbeams; + if (measurenb >= 0) + fmt->measurenb = measurenb; + if (measurebox >= 0) + fmt->measurebox = measurebox; + if (measurefirst >= 0) + fmt->measurefirst = measurefirst; + if (printtempo >= 0) + fmt->printtempo = printtempo; + if (alfa_c >= 0) + fmt->maxshrink = alfa_c; +} + +#ifdef READ_FILE +#include +void alert(const char *form,...) { + va_list ap; + va_start(ap,form); + vprintf(form,ap); + va_end(ap); + putchar('\n'); + fflush(stdout); +} + +/* -- do a tune selection -- */ +static void do_select(struct abctune *t,int tune_nr) +{ int i; + bool print_tune=0; + while (t != 0) { + struct abcsym *s; + + for (s = t->first_sym; s != 0; s = s->sym_next) { + if (s->sym_type == ABC_T_INFO + && s->text[0] == 'X') { + if (sscanf(s->text, "X:%d", &i) == 1 && + (tune_nr < 0 || tune_nr==i)) + print_tune=1; + break; + } + } + do_tune(t,!print_tune); + t->client_data = 1; /* treated */ + t = t->next; + print_tune=0; + } +} + +void usage() { + puts("ABC to Postscript translator."); + puts("Usage:"); + puts(" abcm2ps [options] "); + puts("Options:"); + puts(" -h - usage info"); + puts(" -F - read format file"); + puts(" -s - set scale to nr (default: 0.65)"); + puts(" +l - set landscape mode on"); + puts(" -l - set landscape mode off"); + puts(" -e - select one tune"); + puts("Output file:\n out.ps"); +} + +int main(int argc,const char **argv) { + const char *def_fmt=0; // "/usr/share/amuc/default.fmt"; + int sel_nr=-1; + for (int an=1;an +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +static int keep_comment; + +static int abc_state; /* parse state */ +static int gulen, ulen; /* unit note length set by M: or L: */ +static char *gchord; /* guitar chord */ +static int meter; /* upper value of time sig for n-plets */ +static struct deco dc; /* decorations */ +static int lyric_started; /* lyric started */ +static struct SYMBOL *lyric_start; /* 1st note of the line for d: */ +static struct SYMBOL *lyric_cont; /* current symbol when d: continuation */ +static int vover_bar; /* in a simple voice overlay sequence */ + +#define VOICE_NAME_SZ 64 /* max size of a voice name */ + +static char *file; /* remaining abc file */ +static short linenum; /* current line number */ +static char *scratch_line; /* parse line */ +static int scratch_length = 0; /* allocated length */ +static int line_length; /* current line length */ + +static short nr_voice; /* number of voices (0..n-1) */ +static struct { /* voice table and current pointer */ + char name[32]; /* voice name */ + struct SYMBOL *last_note; /* last note or rest */ + struct SYMBOL *tie; /* last note with starting ties */ + short ulen; /* unit note length */ + char slur; /* number of slur starts */ + char pplet, qplet, rplet; /* nplet - fixme: may be global?*/ + signed char add_pitch; /* key transpose */ + char mvoice; /* main voice when voice overlay */ +} voice_tab[MAXVOICE], *curvoice; + +/* char table for note line parsing */ +enum { + CHAR_BAD, + CHAR_IGN, + CHAR_NOTE, + CHAR_REST, + CHAR_ACC, + CHAR_GRACE, + CHAR_DECO, + CHAR_GCHORD, + CHAR_BSLASH, + CHAR_OBRA, + CHAR_BAR, + CHAR_OPAR, + CHAR_VOV, + CHAR_VOVE, + CHAR_SPAC, + CHAR_MINUS, + CHAR_CPAR, + CHAR_BRHY +}; +static char char_tb[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, CHAR_SPAC, 0, 0, 0, 0, 0, 0, /* 00 - 0f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1f */ + CHAR_SPAC, CHAR_DECO, CHAR_GCHORD, CHAR_BAD, /* (sp) ! " # */ + CHAR_BAD, CHAR_BAD, CHAR_VOV, CHAR_BAD, /* $ % & ' */ + CHAR_OPAR, CHAR_CPAR, CHAR_IGN, CHAR_BAD, /* ( ) * + */ + CHAR_BAD, CHAR_MINUS, CHAR_DECO, CHAR_BAD, /* , - . / */ + CHAR_BAD, CHAR_BAD, CHAR_BAD, CHAR_BAD, /* 0 1 2 3 */ + CHAR_BAD, CHAR_BAD, CHAR_BAD, CHAR_BAD, /* 4 5 6 7 */ + CHAR_BAD, CHAR_BAD, CHAR_BAR, CHAR_BAD, /* 8 9 : ; */ + CHAR_BRHY, CHAR_ACC, CHAR_BRHY, CHAR_BAD, /* < = > ? */ + CHAR_BAD, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, /* @ A B C */ + CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, /* D E F G */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* H I J K */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* L M N O */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* P Q R S */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* T U V W */ + CHAR_DECO, CHAR_DECO, CHAR_REST, CHAR_OBRA, /* X Y Z [ */ + CHAR_BSLASH, CHAR_BAR, CHAR_ACC, CHAR_ACC, /* \ ] ^ _ */ + CHAR_IGN, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, /* ` a b c */ + CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, CHAR_NOTE, /* d e f g */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* h i j k */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* l m n o */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* p q r s */ + CHAR_DECO, CHAR_DECO, CHAR_DECO, CHAR_DECO, /* t u v w */ + CHAR_REST, CHAR_REST, CHAR_REST, CHAR_GRACE, /* x y z { */ + CHAR_BAR, CHAR_BAD, CHAR_DECO, CHAR_BAD, /* | } ~ (del) */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a0 - af */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b0 - bf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c0 - cf */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d0 - df */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e0 - ef */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f0 - ff */ +}; + +static char all_notes[] = "CDEFGABcdefgab^=_"; +char *deco_tb[128]; + +int severity; + +static char *get_line(void); +static char *parse_len(char *p, + int *p_len); +static char *parse_basic_note(char *p, + int *pitch, + int *length, + int *accidental, + int *stemless); +static void parse_header(struct abctune *t, + char *p, + char *comment); +static int parse_line(struct abctune *t, char *p); +static char *parse_note(struct abctune *t, char *p); +static void syntax(const char *msg, char *q); +static void vover_new(void); + +/* -- initialize the parser, return false if already done */ +bool abc_init(int keep_comment_api) +{ + if (scratch_line != 0) + return false; + scratch_line = (char*)malloc(256 + 1); + scratch_length = 256; + keep_comment = keep_comment_api; + return true; +} + +/* -- new symbol -- */ +struct SYMBOL *abc_new(struct abctune *t, + char *p, + char *comment) +{ + struct SYMBOL *s; + + s = (struct SYMBOL*)malloc(sizeof(SYMBOL)); + memset(s, 0, sizeof(SYMBOL)); + s->tune = t; + if (p != 0) { + s->text = (char*)malloc(strlen(p) + 1); + strcpy(s->text,(char*)p); + } + if (comment != 0) { + s->comment = (char*)malloc(strlen(comment) + 1); + strcpy(s->comment, comment); + } + if (t->last_sym == 0) + t->first_sym = t->last_sym = s; + else { + if ((s->sym_next = t->last_sym->sym_next) != 0) + s->sym_next->sym_prev = s; + t->last_sym->sym_next = s; + s->sym_prev = t->last_sym; + t->last_sym = s; + } + s->linenum = linenum; + return s; +} + +/* -- parse an ABC file -- */ +struct abctune *abc_parse(char *file_api) +{ + char *p; + struct abctune *first_tune = 0; + struct abctune *t, *last_tune; + /* initialize */ + file = file_api; + t = 0; + abc_state = ABC_S_GLOBAL; + linenum = 0; + last_tune = 0; + gulen = 0; + + /* scan till end of file */ + for (;;) { + if ((p = get_line()) == 0) { + if (abc_state == ABC_S_HEAD) { + alert("unexpected EOF in header definition"); + severity = 1; + } + break; /* done */ + } + while (isspace(*p)) /* skip starting blanks */ + p++; + + /* start a new tune if not done */ + if (t == 0) { + struct abctune *n; + + if (*p == '\0') + continue; + n = (struct abctune*)malloc(sizeof *n); + memset(n, 0 , sizeof *n); + if (last_tune == 0) + first_tune = n; + else { + last_tune->next = n; + n->prev = last_tune; + } + last_tune = t = n; + ulen = gulen; + } + + /* parse the music line */ + if (!parse_line(t, p)) // (here) + t = 0; + } + return first_tune; +} + +/* -- cut off after % and remove trailing blanks -- */ +static char *decomment_line(char *p) +{ + int i; + char c, *comment = 0; + + i = 0; + for (;;) { + if ((c = *p++) == '%') { + if (i > 0 && p[-2] != '\\') { + if (keep_comment) + comment = p; + c = '\0'; + } + } + if (c == '\0') { + p--; + break; + } + i++; + } + + /* remove trailing blanks */ + while (--i > 0) { + c = *--p; + if (!isspace(c)) { + p[1] = '\0'; + break; + } + } + return comment; +} + +/* -- define a voice by name -- */ +/* the voice is created if it does not exist */ +static char *def_voice(char *p, + int *p_voice) +{ + char *name; + char sep; + int voice; + + name = (char*)p; + while (isalnum(*p) || *p == '_') + p++; + sep = *p; + *p = '\0'; + + if (voice_tab[0].name[0] == '\0') + voice = 0; /* first voice */ + else { + for (voice = 0; voice <= nr_voice; voice++) { + if (strcmp(name, voice_tab[voice].name) == 0) + goto done; + } + if (voice >= MAXVOICE) { + syntax("Too many voices", name); + voice--; + } + } + nr_voice = voice; + strncpy(voice_tab[voice].name, name, sizeof voice_tab[voice].name - 1); + voice_tab[voice].mvoice = voice; +done: + *p_voice = voice; + *p = sep; + return (char*)p; +} + +/* -- treat the broken rhythm '>' and '<' -- */ +static void broken_rhythm(Note *note, + int num) /* >0: do dot, <0: do half */ +{ + int l, m, n; + + num *= 2; + if (num > 0) { + if (num == 6) + num = 8; + n = num * 2 - 1; + for (m = 0; m <= note->nhd; m++) + note->lens[m] = (note->lens[m] * n) / num; + } else { + n = -num; + if (n == 6) + n = 8; + for (m = 0; m <= note->nhd; m++) + note->lens[m] /= n; + } + l = note->lens[0]; + for (m = 1; m <= note->nhd; m++) + if (note->lens[m] < l) + l = note->lens[m]; + note->len = l; +} + +/* -- check for the '!' as end of line (ABC2Win) -- */ +static int check_nl(char *p) +{ + while (*p != '\0') { + switch (*p++) { + case '!': + return 0; + case '|': + case '[': + case ':': + case ']': + case ' ': + case '\t': + return 1; + } + } + return 1; +} + +/* -- skip a clef definition -- */ +static char *clef_skip(char *p, + char **p_name, + char **p_middle) +{ + for (;;) { + if (strncmp(p, "clef=", 5) == 0 + || strncmp(p, "bass", 4) == 0 + || strncmp(p, "treble", 6) == 0 + || strncmp(p, "alto", 4) == 0 + || strncmp(p, "tenor", 5) == 0 + || strncmp(p, "perc", 4) == 0 + || strncmp(p, "none", 4) == 0) { + if (*p_name != 0) + syntax("Double clef name", p); + *p_name = p; + } else if (strncmp(p, "middle=", 7) == 0) { + if (*p_middle != 0) + syntax("Double clef middle", p); + *p_middle = p; + } else break; + while (!isspace(*p) && *p != '\0') + p++; + while (isspace(*p)) + p++; + if (*p == '\0') + break; + } + return p; +} + +/* -- parse a decoration '[!]xxx[!]' -- */ +static char *get_deco(char *p, + char *p_deco) +{ + char *q; + char **t; + int i, l; + + *p_deco = 0; + /* we are after the '!' */ + q = p; + while (*p != '!') { + if (*p == '\0') { + if (q[-1] != '!') + break; + syntax("Decoration not terminated", q); + return p; + } + p++; + } + l = p - q; + if (*p == '!') + p++; + for (i = 1, t = &deco_tb[1]; + *t != 0 && i < 128; + i++, t++) { + if (strlen(*t) == l + && strncmp(*t, q, l) == 0) { + *p_deco = i + 128; + return p; + } + } + + /* new decoration */ + if (i < 128) { + *t = (char*)malloc(l + 1); + memcpy(*t, q, l); + (*t)[l] = '\0'; + *p_deco = i + 128; + } else syntax("Too many decoration types", q); + return p; +} + +/* -- parse a list of accidentals (K:) -- */ +static char *parse_acc(char *p, struct SYMBOL *s) +{ + int pit, len, acc, nostem, nacc; + + nacc = s->un.key.nacc; + for (;;) { + if (nacc >= sizeof s->un.key.pits) { + syntax("Too many accidentals", 0); + break; + } + p = parse_basic_note(p, &pit, &len, &acc, &nostem); + s->un.key.pits[nacc] = pit - curvoice->add_pitch; + s->un.key.accs[nacc++] = acc; + if (*p == '\0') + break; + if (*p != '^' && *p != '_' && *p != '=') + break; + } + s->un.key.nacc = nacc; + return p; +} + +/* -- parse a clef (K: or V:) -- */ +static void parse_clef(struct clef_s *p_clef, + char *name, + char *middle) +{ + int clef = -1; + int transpose = 0; + int clef_line = 2; + int clef_name; + + clef_name = 0; + if (name != 0 && strncmp(name, "clef=", 5) == 0) { + name += 5; + clef_name = 1; + switch (*name) { + case 'g': + transpose = -7; + case 'G': + clef = TREBLE; + break; + case 'f': + transpose = -14; + clef = BASS; + clef_line = 4; + break; + case 'F': + transpose = -7; + clef = BASS; + clef_line = 4; + break; + case 'c': + transpose = -7; + case 'C': + clef = ALTO; + clef_line = 3; + break; + case 'P': + clef = PERC; + break; + default: + clef_name = 0; + } + if (clef_name) { + name++; + while (*name == ',') { + transpose += 7; + name++; + } + while (*name == '\'') { + transpose -= 7; + name++; + } + } + } + if (name != 0 && !clef_name) { + if (!strncmp(name, "bass", 4)) { + clef = BASS; + clef_line = 4; + p_clef->check_pitch = 1; + name += 4; + clef_name = 1; + } else if (!strncmp(name, "treble", 6)) { + clef = TREBLE; + name += 6; + clef_name = 1; + } else if (!strncmp(name, "alto", 4) + || !strncmp(name, "tenor", 5)) { + clef = ALTO; + clef_line = *name == 'a' ? 3 : 4; + p_clef->check_pitch = 1; + if (*name == 'a') + name += 4; + else name += 5; + clef_name = 1; + } else if (!strncmp(name, "perc", 4)) { + clef = PERC; + name += 4; + } else /*if (strncmp(name, "none", 4) == 0)*/ { + clef = TREBLE; + p_clef->invis = 1; + name += 4; + } + + if (clef_name) { + switch (*name) { + case '1': + case '2': + case '3': + case '4': + case '5': + clef_line = *name++ - '0'; + break; + } + if (name[1] == '8') { + if (*name == '-') + p_clef->octave = -1; + else if (*name == '+') + p_clef->octave = 1; + } + } + } + + if (middle != 0) { + int pit, len, acc, nostem, l; + + /* 'middle=' */ + middle += 7; + if (clef < 0) + clef = TREBLE; + curvoice->add_pitch = 0; + parse_basic_note(middle, &pit, &len, &acc, &nostem); + l = 20; + switch (clef) { + case ALTO: + l = 16; + break; + case BASS: + l = 12; + break; + } + l = l - pit + 4 + 14; + clef_line = (l % 7) / 2 + 1; + transpose = l / 7 * 7 - 14; + p_clef->check_pitch = 0; + } + + p_clef->type = clef; + p_clef->line = clef_line; + p_clef->transpose = transpose; + curvoice->add_pitch = transpose; +} + +/* -- parse a 'K:' -- */ +static void parse_key(char *p, + struct SYMBOL *s) +{ + int sf; + char *clef_name, *clef_middle; + + clef_name = 0; + clef_middle = 0; + p = clef_skip(p, &clef_name, &clef_middle); + sf = 0; + switch (*p++) { + case 'F': sf = -1; break; + case 'B': sf++; + case 'E': sf++; + case 'A': sf++; + case 'D': sf++; + case 'G': sf++; + case 'C': break; + case 'H': + s->un.key.bagpipe = 1; + if (*p == 'P') + p++; + else if (*p == 'p') { + sf = 2; + p++; + } else syntax("Unknown bagpipe-like key", p); + break; + case '^': + case '_': + case '=': + p--; /* explicit accidentals */ + break; + case '\0': + s->un.key.empty = 1; + p--; + break; + default: + syntax("Key not recognized", p); + p--; + break; + } + if (*p == '#') { + sf += 7; + p++; + } else if (*p == 'b') { + sf -= 7; + p++; + } + + while (*p != '\0') { + while (isspace(*p)) + p++; + if (*p == '\0') + break; + p = clef_skip(p, &clef_name, &clef_middle); + if (*p == '\0') + break; + switch (*p) { + case 'a': + case 'A': + if (strncasecmp(p, "aeo", 3) == 0) { + sf -= 3; + s->un.key.minor = 1; + } else goto unk; + break; + case 'd': + case 'D': + if (strncasecmp(p, "dor", 3) == 0) + sf -= 2; + else goto unk; + break; + case 'i': + case 'I': + if (strncasecmp(p, "ion", 3) == 0) + break; + goto unk; + case 'l': + case 'L': + if (strncasecmp(p, "loc", 3) == 0) + sf -= 5; + else if (strncasecmp(p, "lyd", 3) == 0) + sf += 1; + else goto unk; + break; + case 'm': + case 'M': + if (strncasecmp(p, "maj", 3) == 0) + ; + else if (strncasecmp(p, "mix", 3) == 0) + sf -= 1; + else if (strncasecmp(p, "min", 3) == 0 + || !isalpha(p[1])) { /* 'm' alone */ + sf -= 3; + s->un.key.minor = 1; + } else goto unk; + break; + case 'o': + case 'O': + if (strncasecmp(p, "octave", 6) == 0) { /* (abcMIDI) */ + p += 6; + while (!isspace(*p) && *p != '\0') + p++; + continue; + } + goto unk; + case 'p': + case 'P': + if (strncasecmp(p, "phr", 3) == 0) + sf -= 4; + else goto unk; + break; + case '^': + case '_': + case '=': + p = parse_acc(p, s); /* explicit accidentals */ + continue; + case '+': + case '-': + if (p[1] == '8') { + /* "+8" / "-8" (fixme: not standard) */ + if (*p == '+') + curvoice->add_pitch += 7; + else curvoice->add_pitch -= 7; + p += 2; + continue; + } + goto unk; + default: + unk: + syntax("Unknown token in key specifier", p); + while (!isspace(*p) && *p != '\0') + p++; + continue; + } + while (isalpha(*p)) + p++; + } + + if (sf > 7 || sf < -7) { + syntax("Too many sharps/flats", p); + if (sf > 0) + sf -= 12; + else sf += 12; + } + s->un.key.sf = sf; + + if (clef_name != 0 || clef_middle != 0) { + s = abc_new(s->tune, 0, 0); + s->sym_type = ABC_T_CLEF; + parse_clef(&s->un.clef, clef_name, clef_middle); + } +} + +/* -- set default length from 'L:' -- */ +static const char *get_len(char *p, + struct SYMBOL *s) +{ + int l1, l2, d; + const char *error_txt = 0; + + l1 = 0; + l2 = 1; + if (sscanf(p, "%d /%d ", &l1, &l2) != 2 + || l1 == 0) { + s->un.length.base_length = ulen ? ulen : BASE_LEN / 8; + return "Bad unit note length: unchanged"; + } + + d = BASE_LEN / l2; + if (d * l2 != BASE_LEN) { + error_txt = "Length incompatible with BASE, using 1/8"; + d = BASE_LEN / 8; + } else { + d *= l1; + if (l1 != 1 + || (l2 & (l2 - 1))) { + error_txt = "Incorrect unit note length, using 1/8"; + d = BASE_LEN / 8; + } + } + s->un.length.base_length = d; + return error_txt; +} + +/* -- get a new line from the current file in memory -- */ +static char *get_line(void) +{ + int l; + char *p; + char *line; + + p = file; + if (*p == '\0') + return 0; + line = p; /* (for syntax error) */ + + /* memorize the beginning of the next line */ + while (*p != '\0' + && *p != '\r' + && *p != '\n') { + p++; + } + l = p - line; + if (*p != '\0') + p++; + /* solve PC-DOS */ + if (p[-1] == '\r' && *p == '\n') + p++; + file = p; + + linenum++; + + /* allocate space for the line */ + if (scratch_line != 0 + && l >= scratch_length) { + free(scratch_line); + scratch_line = 0; + } + if (scratch_line == 0) { + scratch_line = (char*)malloc(l + 1); + scratch_length = l; + } + p = scratch_line; + strncpy(p, line, l); + p[l] = '\0'; + line_length = l; /* for syntax error */ + + return p; +} + +/* -- parse a 'M:' -- */ +static const char *parse_meter(char *p, + struct SYMBOL *s) +{ + int m1, m2, d, wmeasure; + int nm, i; + int in_parenth; + + if (*p == '\0') + return "Empty meter string"; + nm = 0; + in_parenth = 0; + wmeasure = 0; + if (*p == 'N' || *p == 'n') + ; /* no meter */ + else if (*p == 'C') { + s->un.meter.meter[0].top[0] = *p++; + if (*p == '|') + s->un.meter.meter[0].top[1] = *p++; + wmeasure = 4 * BASE_LEN / 4; + nm = 1; + } else while (*p != '\0') { + if (*p == '(' || *p == ')') { + if (*p == '(') + in_parenth = 1; + else in_parenth = 0; + s->un.meter.meter[nm].top[0] = *p++; + nm++; + continue; + } + if (sscanf(p, "%d", &m1) != 1 + || m1 <= 0) + return "Cannot identify meter top"; + i = 0; + m2 = 2; /* default when no bottom value */ + for (;;) { + while (isdigit(*p) + && i < sizeof s->un.meter.meter[0].top) + s->un.meter.meter[nm].top[i++] = *p++; + if (*p == '/') { + p++; + if (sscanf(p, "%d", &m2) != 1 + || m2 <= 0) + return "Cannot identify meter bottom"; + i = 0; + while (isdigit(*p) + && i < sizeof s->un.meter.meter[0].bot) + s->un.meter.meter[nm].bot[i++] = *p++; + break; + } + if (*p != ' ' && *p != '+') + break; + s->un.meter.meter[nm].top[i++] = *p++; + if (sscanf(p, "%d", &d) != 1 + || d <= 0) + return "Cannot identify meter top"; + if (p[-1] == ' ') { + if (d > m1) + m1 = d; + } else m1 += d; + } + if (!in_parenth) + wmeasure += m1 * BASE_LEN / m2; + nm++; + if (*p == ' ' || *p == '+') { + s->un.meter.meter[nm].top[0] = *p++; + nm++; + } + } + + s->un.meter.wmeasure = wmeasure; + s->un.meter.nmeter = nm; + + /* if in the header, change the unit note length */ + if (abc_state == ABC_S_HEAD && ulen == 0) { + if (wmeasure >= BASE_LEN * 3 / 4 + || wmeasure == 0) + ulen = BASE_LEN / 8; + else ulen = BASE_LEN / 16; + } + + return 0; +} + +/* -- treat %%staves -- */ +static void get_staves(char *p, + struct SYMBOL *s) +{ + int voice; + char flags, flags2; + struct staff_s *staff; + + /* define the voices */ + flags = 0; + staff = 0; + voice = 0; + while (*p != '\0') { + switch (*p) { + case ' ': + case '\t': + break; + case '[': + if (flags & (OPEN_BRACKET | OPEN_BRACE | OPEN_PARENTH)) + goto err; + flags |= OPEN_BRACKET; + staff = 0; + break; + case ']': + if (staff == 0) + goto err; + staff->flags |= CLOSE_BRACKET; + break; + case '{': + if (flags & (OPEN_BRACKET | OPEN_BRACE | OPEN_PARENTH)) + goto err; + flags |= OPEN_BRACE; + staff = 0; + break; + case '}': + if (staff == 0) + goto err; + staff->flags |= CLOSE_BRACE; + break; + case '(': + if (flags & OPEN_PARENTH) + goto err; + flags |= OPEN_PARENTH; + staff = 0; + break; + case ')': + if (staff == 0) + goto err; + staff->flags |= CLOSE_PARENTH; + break; + case '|': + if (staff == 0) + goto err; + staff->flags |= STOP_BAR; + break; + default: + if (!isalnum(*p) && *p != '_') + goto err; + { + int v; + + p = def_voice(p, &v); + staff = &s->un.staves[voice]; + voice++; + staff->voice = v; + staff->name = (char*)malloc(strlen(voice_tab[v].name) + 1); + strcpy(staff->name, voice_tab[v].name); + } + staff->flags = flags; + flags = 0; + continue; + } + p++; + } + + /* check for errors */ + if (flags != 0) + goto err; + + flags = CLOSE_BRACKET | CLOSE_BRACE | CLOSE_PARENTH; /* bad flags */ + flags2 = flags; + for (voice = 0, staff = s->un.staves; + voice <= MAXVOICE && staff->name; + voice++, staff++) { + // if (staff->flags & flags) goto err; + if (staff->flags & CLOSE_PARENTH) + flags = flags2; + if (staff->flags & OPEN_BRACKET) { + flags &= ~CLOSE_BRACKET; + flags |= OPEN_BRACKET | OPEN_BRACE; + } else if (staff->flags & CLOSE_BRACKET) { + flags &= ~(OPEN_BRACKET | OPEN_BRACE); + flags |= CLOSE_BRACKET; + } else if (staff->flags & OPEN_BRACE) { + flags &= ~CLOSE_BRACE; + flags |= OPEN_BRACKET | OPEN_BRACE; + } else if (staff->flags & CLOSE_BRACE) { + flags &= ~(OPEN_BRACKET | OPEN_BRACE); + flags |= CLOSE_BRACE; + } + if (staff->flags & OPEN_PARENTH) { + flags2 = flags; + flags &= ~CLOSE_PARENTH; + } + } + return; + +err: + syntax("%%%%staves error", p); +} + +/* -- get a possibly quoted string -- */ +char *get_str(char *d, /* destination */ + char *s, /* source */ + int maxlen) /* max length */ +{ + char sep, c; + + maxlen--; /* have place for the EOS */ + while (isspace(*s)) + s++; + + if (*s == '"') { + sep = '"'; + s++; + } else sep = ' '; + while ((c = *s) != '\0') { + if (c == sep + || (c == '\t' && sep == ' ')) { + if (sep != ' ') + s++; + break; + } + if (c == '\\' + && (c == sep + || (c == '\t' && sep == ' '))) { + s++; + continue; + } + if (--maxlen > 0) + *d++ = c; + s++; + } + *d = '\0'; + while (isspace(*s)) + s++; + return s; +} + +/* -- parse a tempo (Q:) -- */ +static char *parse_tempo(char *p, + struct SYMBOL *s) +{ + int have_error = 0; + char *q; + int l; + + /* string before */ + if (*p == '"') { + q = ++p; + while (*p != '"' && *p != '\0') + p++; + l = p - q; + s->un.tempo.str1 = (char*)malloc(l + 1); + strncpy(s->un.tempo.str1, q, l); + s->un.tempo.str1[l] = '\0'; + if (*p == '"') + p++; + while (isspace(*p)) + p++; + } + + /* beat */ + if (*p == 'C' || *p == 'c' + || *p == 'L' || *p == 'l') { + int len; + + p = parse_len(p + 1, &len); + if (len <= 0) + have_error++; + else s->un.tempo.length[0] = len; + while (isspace(*p)) + p++; + } else if (isdigit(*p) && strchr(p, '/') != 0) { + int i; + + i = 0; + while (isdigit(*p)) { + int top, bot, n; + + if (sscanf(p, "%d /%d%n", &top, &bot, &n) != 2 + || bot <= 0) { + have_error++; + break; + } + l = (BASE_LEN * top) / bot; + if (l <= 0 + || i >= sizeof s->un.tempo.length + / sizeof s->un.tempo.length[0]) + have_error++; + else s->un.tempo.length[i++] = l; + p += n; + while (isspace(*p)) + p++; + } + } + + /* tempo value ('Q:beat=value' or 'Q:value') */ + if (*p == '=') + p++; + if (isdigit(*p)) { + int value; + + value = atoi(p); + if (value < 0) + have_error++; + else s->un.tempo.value = value; + while (isdigit(*p) || isspace(*p)) + p++; + } + + /* string after */ + if (*p == '"') { + q = ++p; + while (*p != '"' && *p != '\0') + p++; + l = p - q; + s->un.tempo.str2 = (char*)malloc(l + 1); + strncpy(s->un.tempo.str2, q, l); + s->un.tempo.str2[l] = '\0'; + } + + return have_error ? (char*)"Invalid tempo" : 0; +} + +/* -- get a user defined accent (U:) -- */ +static char *get_user(char *p, + struct SYMBOL *s) +{ + if (char_tb[*p] != CHAR_DECO) /* accept any character */ +/*fixme: should be for the current tune only */ + char_tb[*p] = CHAR_DECO; + s->un.user.symbol = *p++; + + /* '=' and '!' are not important */ + while (isspace(*p) + || *p == '=' || *p == '!') + p++; + get_deco(p, &s->un.user.value); + return 0; +} + +/* -- parse the voice parameters (V:) -- */ +static char *parse_voice(char *p, + struct SYMBOL *s) +{ + int voice; + char *error_txt = 0; + char name[VOICE_NAME_SZ]; + char *clef_name, *clef_middle; + static struct kw_s { + const char *name; + short len; + short index; + } kw_tb[] = { + {"name=", 5, 0}, + {"nm=", 3, 0}, + {"subname=", 7, 1}, + {"sname=", 6, 1}, + {"snm=", 4, 1}, + {"merge", 5, 2}, + {"up", 2, 3}, + {"down", 4, 4}, + {0, 0, 0} + }; + struct kw_s *kw; + + /* save the unit note length of the previous voice */ + curvoice->ulen = ulen; + + if (voice_tab[0].name[0] == '\0') { + switch (s->sym_prev->sym_type) { + case ABC_T_EOLN: + case ABC_T_NOTE: + case ABC_T_REST: + case ABC_T_BAR: + /* the previous voice was implicit (after K:) */ + voice_tab[0].name[0] = '1'; + break; + } + } + p = def_voice(p, &voice); + + curvoice = &voice_tab[voice]; + s->un.voice.voice = voice; + s->un.voice.name = (char*)malloc(strlen(curvoice->name) + 1); + strcpy(s->un.voice.name, curvoice->name); + + /* if in tune, set the unit note length */ + if (abc_state == ABC_S_TUNE || abc_state == ABC_S_EMBED) + ulen = curvoice->ulen; + + /* parse the other parameters */ + clef_name = 0; + clef_middle = 0; + for (;;) { + while (isspace(*p)) + p++; + if (*p == '\0') + break; + p = clef_skip(p, &clef_name, &clef_middle); + if (*p == '\0') + break; + for (kw = kw_tb; kw->name; kw++) { + if (strncmp(p, kw->name, kw->len) == 0) + break; + } + if (!kw->name) { + while (!isspace(*p) && *p != '\0') + p++; /* ignore unknown keywords */ + continue; + } + p += kw->len; + switch (kw->index) { + case 0: /* name */ + p = get_str(name, p, VOICE_NAME_SZ); + s->un.voice.fname = (char*)malloc(strlen(name) + 1); + strcpy(s->un.voice.fname, name); + break; + case 1: /* subname */ + p = get_str(name, p, VOICE_NAME_SZ); + s->un.voice.nname = (char*)malloc(strlen(name) + 1); + strcpy(s->un.voice.nname, name); + break; + case 2: /* merge */ + s->un.voice.merge = 1; + break; + case 3: /* up */ + s->un.voice.stem = 1; + break; + case 4: /* down */ + s->un.voice.stem = -1; + break; + } + } + if (clef_name != 0 || clef_middle != 0) { + s = abc_new(s->tune, 0, 0); + s->sym_type = ABC_T_CLEF; + parse_clef(&s->un.clef, clef_name, clef_middle); + } + return error_txt; +} + +/* -- sort the notes in a chord (lowest first) -- */ +void note_sort(struct SYMBOL *s) +{ + int m = s->un.note.nhd; + + for (;;) { + int i; + int nx = 0; + + for (i = 1; i <= m; i++) { + if (s->un.note.pits[i] < s->un.note.pits[i-1]) { + int k; +#define xch(a, b) k = a; a = b; b = k + xch(s->un.note.pits[i], s->un.note.pits[i-1]); + xch(s->un.note.lens[i], s->un.note.lens[i-1]); + xch(s->un.note.accs[i], s->un.note.accs[i-1]); + xch(s->un.note.sl1[i], s->un.note.sl1[i-1]); + xch(s->un.note.sl2[i], s->un.note.sl2[i-1]); + xch(s->un.note.ti1[i], s->un.note.ti1[i-1]); + xch(s->un.note.ti2[i], s->un.note.ti2[i-1]); +#undef xch + nx++; + } + } + if (nx == 0) + break; + } +} + +/* -- parse a bar -- */ +static char *parse_bar(struct abctune *t, + char *p) +{ + struct SYMBOL *s; + int bar_type; + char repeat_value[32]; + + p--; + bar_type = 0; + for (;;) { + switch (*p++) { + case '|': + bar_type <<= 4; + bar_type |= B_BAR; + continue; + case '[': + bar_type <<= 4; + bar_type |= B_OBRA; + continue; + case ']': + bar_type <<= 4; + bar_type |= B_CBRA; + continue; + case ':': + bar_type <<= 4; + bar_type |= B_COL; + continue; + default: + break; + } + break; + } + p--; + if ((bar_type & 0x0f) == B_OBRA && bar_type != B_OBRA) { + bar_type >>= 4; /* have an other bar for '[' */ + p--; + } + if (bar_type == (B_OBRA << 8) + (B_BAR << 4) + B_CBRA) /* [|] */ + bar_type = (B_OBRA << 4) + B_CBRA; /* [] */ + +/* curvoice->last_note = 0; */ + if (vover_bar) { + curvoice = &voice_tab[curvoice->mvoice]; + vover_bar = 0; + } + s = abc_new(t, gchord, 0); + if (gchord) { + free(gchord); + gchord = 0; + } + s->sym_type = ABC_T_BAR; + s->state = ABC_S_TUNE; + s->un.bar.type = bar_type; + + if (dc.n > 0) { + memcpy(&s->un.bar.dc, &dc, sizeof s->un.bar.dc); + dc.n = 0; + } + if (!isdigit(*p) /* if not a repeat bar */ + && (*p != '"' || p[-1] != '[')) { /* ('["' only) */ + int n; + + if (*p != '/') + return p; + + /* measure repeat */ + n = 0; + while (*p == '/') { + n++; + p++; + } + s = abc_new(t, 0, 0); + s->sym_type = ABC_T_MREP; + s->state = ABC_S_TUNE; + s->un.bar.type = 0; + s->un.bar.len = n; + return p; + } + + if (*p == '"') + p = get_str(repeat_value, p, sizeof repeat_value); + else { + char *q; + + q = repeat_value; + while (isdigit(*p) + || *p == ',' + || *p == '-' + || (*p == '.' && isdigit(p[1]))) { + if (q < &repeat_value[sizeof repeat_value - 1]) + *q++ = *p++; + else p++; + } + *q = '\0'; + } + if (repeat_value[0] != '1' || repeat_value[1] != '\0') + curvoice->tie = 0; + if (bar_type != B_OBRA + || s->text != 0) { + s = abc_new(t, repeat_value, 0); + s->sym_type = ABC_T_BAR; + s->state = ABC_S_TUNE; + s->un.bar.type = B_OBRA; + } else { + s->text = (char*)malloc(strlen(repeat_value) + 1); + strcpy(s->text, repeat_value); + } + s->un.bar.repeat_bar = 1; + return p; +} + +/* -- parse note or rest with pitch and length -- */ +static char *parse_basic_note(char *p, + int *pitch, + int *length, + int *accidental, + int *stemless) +{ + int pit, len, acc, nostem; + + acc = pit = nostem = 0; + + /* look for accidental sign */ + switch (*p) { + case '^': + p++; + if (*p == '^') { + acc = A_DS; + p++; + } else acc = A_SH; + break; + case '=': + p++; + acc = A_NT; + break; + case '_': + p++; + if (*p == '_') { + acc = A_DF; + p++; + } else acc = A_FT; + break; + } + { + char *p_n; + + p_n = strchr(all_notes, *p); + if (p_n == 0 + || p_n - all_notes >= 14) { + if (acc) + syntax("Missing note after accidental", + p); + else syntax("Not a note", p); + pit = 16 + 7; /* 'c' */ + } else pit = p_n - all_notes + 16; + p++; + } + + while (*p == '\'') { /* eat up following ' chars */ + pit += 7; + p++; + } + + while (*p == ',') { /* eat up following , chars */ + pit -= 7; + p++; + } + + if (*p == '0') { + nostem = 1; + p++; + } + + p = parse_len(p, &len); + len = len * ulen / BASE_LEN; + + *pitch = pit + curvoice->add_pitch; + *length = len; + *accidental = acc; + *stemless = nostem; + + return p; +} + +/* -- parse for decoration on note/bar -- */ +char *parse_deco(char *p, + struct deco *deco) +{ + int n; + char c, d; + + n = deco->n; + for (;;) { + c = *p++; + if (char_tb[c] != CHAR_DECO) + break; + + d = c; + if (c == '!') + p = get_deco(p, &d); + if (n >= MAXDC) + syntax("Too many decorations for the note", p); + else deco->t[n++] = d; + } + deco->n = n; + return p - 1; +} + +/* -- parse a decoration line (d:) -- */ +static const char *parse_decoline(char *p) +{ + struct SYMBOL *is; + char d; + int n; + + if ((is = lyric_cont) == 0) + is = lyric_start; + else lyric_cont = 0; + + /* scan the decoration line */ + while (*p != '\0') { + while (isspace(*p)) + p++; + if (*p == '\0') + break; + switch (*p) { + case '|': + while (is != 0 && is->sym_type != ABC_T_BAR) + is = is->sym_next; + if (is == 0) { + syntax("Not enough bar lines for lyric line", p); + return 0; + } + is = is->sym_next; + p++; + continue; + case '*': + while (is != 0 && is->sym_type != ABC_T_NOTE) + is = is->sym_next; + if (is == 0) { + syntax("Not enough notes for decoration line", p); + return 0; + } + is = is->sym_next; + p++; + continue; + case '\\': + if (p[1] == '\0') { + if (is == 0) + return "Not enough notes for decoration line"; + lyric_cont = is; + return 0; + } + syntax("'\\' ignored", p); + p++; + continue; + case '!': + p = get_deco(p + 1, &d); + break; + default: + d = *p++; + break; + } + + /* store the decoration in the next note */ + while (is != 0 && is->sym_type != ABC_T_NOTE) + is = is->sym_next; + if (is == 0) + return "Not enough notes for decoration line"; + + n = is->un.note.dc.n; + if (n >= MAXDC) + syntax("Too many decorations for the note", p); + is->un.note.dc.t[n] = d; + is->un.note.dc.n = n + 1; + is = is->sym_next; + } + return 0; +} + +/* -- parse a note length -- */ +static char *parse_len(char *p, + int *p_len) +{ + int len, fac; + + len = BASE_LEN; + if (isdigit(*p)) { + len *= strtol(p, 0, 10); + while (isdigit(*p)) + p++; + } + fac = 1; + while (*p == '/') { + p++; + if (isdigit(*p)) { + fac *= strtol(p, 0, 10); + while (isdigit(*p)) + p++; + } else fac *= 2; + if (len % fac) { + syntax("Bad length divisor", p - 1); + break; + } + } + len /= fac; + *p_len = len; + return p; +} + +/* -- parse a music line -- */ +/* return 0 at end of tune */ +static int parse_line(struct abctune *t, + char *p) +{ + struct SYMBOL *s; + char *comment; + struct SYMBOL *last_note_sav = 0; + char *q, c; + int i; + char sappo = 0; + static char qtb[10] = {1, 1, 3, 2, 3, 0, 2, 0, 3, 0}; + +again: /* for history */ + switch (*p) { + case '\0': + switch (abc_state) { + case ABC_S_GLOBAL: + case ABC_S_HEAD: /*fixme: may have blank lines in headers*/ + if (keep_comment) { + s = abc_new(t, 0, 0); + s->sym_type = ABC_T_NULL; + s->state = abc_state; + } + return 1; + } + abc_state = ABC_S_GLOBAL; + return 0; + case '%': + if (p[1] == '%') { + comment = decomment_line(p + 2); + s = abc_new(t, p, comment); + s->sym_type = ABC_T_PSCOM; + s->state = abc_state; + p += 2; /* skip '%%' */ + if (strncasecmp(p, "fmt ", 4) == 0) + p += 4; /* skip 'fmt' */ + if (strncmp(p, "begintext", 9) == 0) { + for (;;) { + if ((p = get_line()) == 0) { + syntax("EOF while parsing %%begintext pseudo-comment", + scratch_line); + return 0; + } + s = abc_new(t, p, 0); + s->sym_type = ABC_T_PSCOM; + s->state = abc_state; + if (*p != '%' || p[1] != '%') + continue; + p += 2; + if (strncasecmp(p, "fmt ", 4) == 0) + p += 4; + if (strncmp(p, "endtext", 7) == 0) + return 1; + } + /* not reached */ + } + if (strncmp(p, "staves ", 7) == 0) + get_staves(p + 7, s); + return 1; + } + if (keep_comment) { + s = abc_new(t, p, 0); + s->sym_type = ABC_T_NULL; + s->state = abc_state; + } + return 1; /* skip comments */ + } + comment = decomment_line(p); + + /* header fields */ + if (p[1] == ':' + && *p != '|' && *p != ':') { /* not '|:' nor '::' */ + parse_header(t, p, comment); + if (*p == 'H') { + + /* wait for an other 'x:' or any '%%' */ + for (;;) { + if ((p = get_line()) == 0) + break; + if (p[1] == ':' + || (p[1] == '%' && *p == '%')) + goto again; + if (abc_state == ABC_S_HEAD) { + s = abc_new(t, p, 0); + s->sym_type = ABC_T_INFO2; + s->state = abc_state; + } + } + } + + /* handle BarFly voice definition */ + /* 'V:n ' */ + if (*p != 'V' + || abc_state != ABC_S_TUNE) + return 1; + c = p[strlen(p) - 1]; + if (c != '|' && c != ']') + return 1; + while (!isspace(*p) && *p != '\0') + p++; + while (isspace(*p)) + p++; + } + if (abc_state != ABC_S_TUNE) { + if (keep_comment) { + s = abc_new(t, p, comment); + s->sym_type = ABC_T_NULL; + s->state = abc_state; + } + return 1; + } + + if (scratch_line[0] == ' ' && curvoice->last_note != 0) + curvoice->last_note->un.note.word_end = 1; + + lyric_started = 0; + lyric_start = lyric_cont = 0; + while (*p != '\0') { + switch (char_tb[*p++]) { + case CHAR_GCHORD: { /* " */ + int l; + int more_gch; + + gch_continue: + more_gch = 0; + q = p; + while (*p != '"') { + if (*p == '\0') { + more_gch = 1; + break; + } + p++; + } + l = p - q; + if (gchord) { + int l2; + char *gch; + + /* many guitar chord: concatenate with '\n' */ + l2 = strlen(gchord); + gch = (char*)malloc(l2 + 1 + l + 1); + strcpy(gch, gchord); + gch[l2++] = '\n'; + strncpy(&gch[l2], q, l); + gch[l2 + l] = '\0'; + free(gchord); + gchord = gch; + } else { + gchord = (char*)malloc(l + 1); + strncpy(gchord, q, l); + gchord[l] = '\0'; + } + if (*p != '\0') + p++; + else if (more_gch) { + if ((p = get_line()) == 0) { + syntax("EOF reached while parsing guitar chord", + q); + return 0; + } + goto gch_continue; + } + } + break; + case CHAR_GRACE: /* '{' or '}' */ + if (p[-1] == '{') { + if (*p == '/') { + sappo = 1; + p++; + } + char_tb['{'] = CHAR_BAD; + char_tb['}'] = CHAR_GRACE; + last_note_sav = curvoice->last_note; + curvoice->last_note = 0; + } else { + char_tb['{'] = CHAR_GRACE; + char_tb['}'] = CHAR_BAD; +/*fixme:bad*/ + t->last_sym->un.note.word_end = 1; + curvoice->last_note = last_note_sav; + } + break; + case CHAR_DECO: + if (p[-1] == '!' && check_nl(p)) { + s = abc_new(t, 0, 0); /* abc2win EOL */ + s->sym_type = ABC_T_EOLN; + s->state = abc_state; + break; + } + if (p[-1] == '.' && *p == '|') { + p = parse_bar(t, p + 1); +/*fixme: should have other dashed bars, as '.||' */ + t->last_sym->un.bar.type = B_COL; + break; + } + p = parse_deco(p - 1, &dc); + break; + case CHAR_ACC: + case CHAR_NOTE: + case CHAR_REST: + p = parse_note(t, p - 1); + if (sappo) { + t->last_sym->un.note.sappo = 1; + sappo = 0; + } + curvoice->last_note = t->last_sym; + break; + case CHAR_BSLASH: /* '\\' */ +/*fixme: KO if in grace note sequence*/ + if (*p == '\0') + return 1; + syntax("'\\' ignored", p - 1); + break; + case CHAR_OBRA: /* '[' */ + if (*p == '|' || *p == ']' + || isdigit(*p) || *p == '"') { + p = parse_bar(t, p); + break; + } + if (p[1] != ':') { + p = parse_note(t, p - 1); /* chord */ + if (sappo) { + t->last_sym->un.note.sappo = 1; + sappo = 0; + } + curvoice->last_note = t->last_sym; + break; + } + + /* embedded header */ + c = ']'; + q = p; + while (*p != '\0' && *p != c) + p++; + if (*p == '\0') { + syntax("Escape sequence [..] not closed", + q); + c = '\0'; + } else *p = '\0'; + abc_state = ABC_S_EMBED; + parse_header(t, q, 0); + abc_state = ABC_S_TUNE; + *p++ = c; + break; + case CHAR_BAR: /* '|', ':' or ']' */ + p = parse_bar(t, p); + break; + case CHAR_OPAR: /* '(' */ + if (isdigit(*p)) { + curvoice->pplet = *p - '0'; + if (curvoice->pplet <= 1) { + syntax("Invalid 'p' in tuplet", p); + curvoice->pplet = 0; + } + curvoice->qplet = qtb[curvoice->pplet]; + curvoice->rplet = curvoice->pplet; + p++; + if (*p == ':') { + p++; + if (isdigit(*p)) { + curvoice->qplet = *p - '0'; + p++; + } + if (*p == ':') { + p++; + if (isdigit(*p)) { + if (curvoice->pplet != 0) + curvoice->rplet = *p - '0'; + p++; + } + } + } + if (curvoice->qplet == 0) + curvoice->qplet = meter % 3 == 0 + ? 3 + : 2; + } else if (*p == '&') { + s = abc_new(t, 0, 0); + s->sym_type = ABC_T_V_OVER; + p++; + if (*p == '&') { + s->un.v_over.type = V_OVER_SD; + p++; + } else s->un.v_over.type = V_OVER_SS; + s->un.v_over.voice = curvoice - voice_tab; + char_tb[')'] = CHAR_VOVE; + } else curvoice->slur++; + break; + case CHAR_CPAR: /* ')' */ + switch (t->last_sym->sym_type) { + case ABC_T_NOTE: + case ABC_T_REST: + break; + default: + goto bad_char; + } + t->last_sym->un.note.slur_end++; + break; + case CHAR_VOV: /* '&' */ + if (*p != ')') { + s = abc_new(t, 0, 0); + s->sym_type = ABC_T_V_OVER; + if (*p == '&') { + s->un.v_over.type = V_OVER_D; + p++; + } /*else s->un.v_over.type = V_OVER_S; */ + vover_new(); + s->un.v_over.voice = curvoice - voice_tab; + if (char_tb[')'] != CHAR_VOVE) + vover_bar = 1; + break; + } + if (char_tb[')'] != CHAR_VOVE) { + syntax("Bad end of voice overlay", p - 1); + break; + } + p++; + /* fall thru */ + case CHAR_VOVE: /* ')' after '(&' */ + s = abc_new(t, 0, 0); + s->sym_type = ABC_T_V_OVER; + s->un.v_over.type = V_OVER_E; + s->un.v_over.voice = curvoice->mvoice; + char_tb[')'] = CHAR_CPAR; + if (curvoice->last_note != 0) { + curvoice->last_note->un.note.word_end = 1; + curvoice->last_note = 0; + } + curvoice = &voice_tab[curvoice->mvoice]; + break; + case CHAR_SPAC: /* ' ' and '\t' */ + if (curvoice->last_note != 0) + curvoice->last_note->un.note.word_end = 1; + break; + case CHAR_MINUS: /* '-' */ + if ((curvoice->tie = curvoice->last_note) == 0 + || curvoice->tie->sym_type != ABC_T_NOTE) + goto bad_char; + for (i = 0; i <= curvoice->tie->un.note.nhd; i++) + curvoice->tie->un.note.ti1[i] = 1; + break; + case CHAR_BRHY: /* '>' and '<' */ + if (curvoice->last_note == 0) + goto bad_char; + i = 1; + while (*p == p[-1]) { + i++; + p++; + } + if (i > 3) { + syntax("Bad broken rhythm", p - 1); + i = 3; + } + if (p[-1] == '<') + i = -i; + broken_rhythm(&curvoice->last_note->un.note, i); + curvoice->last_note->un.note.brhythm = i; + break; + case CHAR_IGN: /* '*' & '`' */ + break; + default: + bad_char: + syntax("Bad character", p - 1); + break; + } + } + +/*fixme: may we have grace notes across lines?*/ + if (char_tb['{'] == CHAR_BAD) { + syntax("No end of grace note sequence", 0); + char_tb['{'] = CHAR_GRACE; + char_tb['}'] = CHAR_BAD; + if (curvoice->last_note != 0) + curvoice->last_note->un.note.word_end = 1; + curvoice->last_note = last_note_sav; + } + + /* add eoln */ + s = abc_new(t, 0, 0); + s->sym_type = ABC_T_EOLN; + s->state = abc_state; + + return 1; +} + +/* -- parse a note -- */ +static char *parse_note(struct abctune *t, + char *p) +{ + struct SYMBOL *s; + char *q; + int pit, len, acc, nostem; + int chord, sl1, sl2; + int j, m; + int ntie; + signed char tie_pit[MAXHD]; + + s = abc_new(t, gchord, 0); + s->sym_type = ABC_T_NOTE; + s->state = ABC_S_TUNE; + if (gchord) free(gchord); + gchord = 0; + + if (dc.n > 0) { + memcpy(&s->un.note.dc, &dc, sizeof s->un.note.dc); + dc.n = 0; + } + + if (char_tb['{'] == CHAR_BAD) /* in a grace note sequence */ + s->un.note.grace = 1; + else if (curvoice->rplet) { /* start of n-plet */ + s->un.note.p_plet = curvoice->pplet; + s->un.note.q_plet = curvoice->qplet; + s->un.note.r_plet = curvoice->rplet; + curvoice->rplet = 0; + } + + /* rest */ + switch (*p) { + case 'Z': /* multi-rest */ + s->sym_type = ABC_T_MREST; + p++; + len = 1; + if (isdigit(*p)) { + len = strtol(p, 0, 10); + while (isdigit(*p)) + p++; + } + s->un.bar.type = 0; + s->un.bar.len = len; + return p; + case 'y': /* space (BarFly) */ + s->sym_type = ABC_T_REST; + s->un.note.invis = 1; + s->un.note.slur_st += curvoice->slur; + curvoice->slur = 0; + return p + 1; + case 'x': /* invisible rest */ + s->un.note.invis = 1; + /* fall thru */ + case 'z': + s->sym_type = ABC_T_REST; + p = parse_len(p + 1, &len); + s->un.note.len = s->un.note.lens[0] = len * ulen / BASE_LEN; + if (curvoice->last_note != 0 + && curvoice->last_note->un.note.brhythm != 0) + broken_rhythm(&s->un.note, + -curvoice->last_note->un.note.brhythm); + return p; + } + + if (!s->un.note.grace && !lyric_started) { + lyric_started = 1; + s->un.note.lyric_start = 1; + lyric_start = s; + } + + chord = 0; + q = p; + if (*p == '[') { /* accept only '[..]' for chord */ + chord = 1; + p++; + } + + /* prepare searching the end of ties */ + ntie = 0; + if (curvoice->tie != 0) { + for (m = 0; m <= curvoice->tie->un.note.nhd; m++) { + if (curvoice->tie->un.note.ti1[m]) + tie_pit[ntie++] = curvoice->tie->un.note.pits[m]; + } + curvoice->tie = 0; + } + + /* get pitch and length */ + m = 0; + sl1 = sl2 = 0; + nostem = 0; + for (;;) { + int tmp; + + if (chord && *p == '(') { + s->un.note.sl1[m] = ++sl1; + p++; + } + p = parse_deco(p, &dc); /* for extra decorations within chord */ + if (strchr(all_notes, *p) == 0) { + syntax("Not a note", p); + p++; + } else { + p = parse_basic_note(p, + &pit, + &len, + &acc, + &tmp); + if (s->un.note.grace) { + len = len * BASE_LEN / 4 / ulen; + tmp = 0; + } + s->un.note.pits[m] = pit; + s->un.note.lens[m] = len; + s->un.note.accs[m] = acc; + nostem |= tmp; + + for (j = 0; j < ntie; j++) { + if (tie_pit[j] == pit) { + s->un.note.ti2[m] = 1; + tie_pit[j] = -128; + break; + } + } + + if (chord) { + if (*p == '-') { + s->un.note.ti1[m] = 1; + curvoice->tie = s; + p++; + } + if (*p == ')') { + s->un.note.sl2[m] = ++sl2; + p++; + if (*p == '-') { + s->un.note.ti1[m] = 1; + p++; + } + } + } + m++; + } + + if (!chord) + break; + if (*p == ']') { + p++; + break; + } + if (*p == '\0') { + syntax("Chord not closed", q); + return p; + } + } + s->un.note.stemless = nostem; + + /* warn about the bad ties */ + if (char_tb['{'] != CHAR_BAD) { /* if not in a grace note sequence */ + for (j = 0; j < ntie; j++) { + if (tie_pit[j] != -128) + syntax("Bad tie", p); + } + } + + if (m == 0) { /* if no note */ + if ((t->last_sym = s->sym_prev) == 0) + t->first_sym = 0; + else s->sym_prev->sym_next = 0; + return p; + } + s->un.note.nhd = m - 1; + + /* the chord length is the length of the first note */ + s->un.note.len = s->un.note.lens[0]; + + note_sort(s); /* sort the notes in chord */ + if (curvoice->last_note != 0 + && curvoice->last_note->un.note.brhythm != 0) + broken_rhythm(&s->un.note, + -curvoice->last_note->un.note.brhythm); + s->un.note.slur_st += curvoice->slur; + curvoice->slur = 0; + return p; +} + +/* -- process a header -- */ +static void parse_header(struct abctune *t, + char *p, + char *comment) +{ + struct SYMBOL *s; + char header_type = *p; + const char *error_txt = 0; + + s = abc_new(t, p, comment); + s->sym_type = ABC_T_INFO; + s->state = abc_state; + + p += 2; + while (isspace(*p)) + p++; + switch (header_type) { + case 'd': + if (lyric_start == 0) { + error_txt = "Erroneous 'd:'"; + break; + } + error_txt = parse_decoline(p); + break; + case 'K': + if (abc_state == ABC_S_GLOBAL) + break; + parse_key(p, s); + if (abc_state == ABC_S_HEAD) { + int i; + + abc_state = ABC_S_TUNE; + if (ulen == 0) + ulen = BASE_LEN / 8; + for (i = MAXVOICE; --i >= 0; ) + voice_tab[i].ulen = ulen; + } + break; + case 'L': + error_txt = get_len(p, s); + ulen = s->un.length.base_length; + if (abc_state == ABC_S_GLOBAL) + gulen = ulen; + break; + case 'M': + error_txt = parse_meter(p, s); + break; + case 'Q': + error_txt = parse_tempo(p, s); + break; + case 'U': + error_txt = get_user(p, s); + break; + case 'V': + if (abc_state == ABC_S_GLOBAL) + break; + error_txt = parse_voice(p, s); + break; + case 'T': + if (abc_state != ABC_S_GLOBAL) + break; + /* 'T:' may start a new tune without 'X:' */ + alert("T: without X: (line %d)",linenum); + /* fall thru */ + case 'X': + if (abc_state != ABC_S_GLOBAL) { + error_txt = "Previous tune not closed properly"; + /*??maybe call end_tune if ABC_S_TUNE??*/ + } + memset(voice_tab, 0, sizeof voice_tab); + nr_voice = 0; + curvoice = &voice_tab[0]; + abc_state = ABC_S_HEAD; + break; + } + if (error_txt != 0) + syntax(error_txt, p); +} + +/* -- sytax: print message for syntax error -- */ +static void syntax(const char *msg, + char *q) +{ + int n; + + severity = 1; + n = q - scratch_line; + if (n >= line_length) alert("%s (line:%d)",msg,linenum); + else alert("%s (line:%d char:%d)",msg,linenum,n); +} + +/* -- switch to a new voice overlay -- */ +static void vover_new(void) +{ + int voice, mvoice; + + mvoice = curvoice - voice_tab; + for (voice = mvoice + 1; voice <= nr_voice; voice++) + if (voice_tab[voice].mvoice == mvoice) + break; + if (voice > nr_voice) { + if (nr_voice >= MAXVOICE) { + syntax("Too many voices", 0); + return; + } + nr_voice = voice; + voice_tab[voice].name[0] = '&'; + voice_tab[voice].mvoice = mvoice; + } + voice_tab[voice].ulen = curvoice->ulen; + voice_tab[voice].add_pitch = curvoice->add_pitch; + curvoice = &voice_tab[voice]; +} diff --git a/src-abcm2ps/abcparse.h b/src-abcm2ps/abcparse.h new file mode 100644 index 0000000..ef9c493 --- /dev/null +++ b/src-abcm2ps/abcparse.h @@ -0,0 +1,214 @@ +/*++ + * Declarations for abcparse.c. + * + *-*/ + +#define MAXVOICE 32 /* max number of voices */ + +#define MAXHD 8 /* max heads on one stem */ +#define MAXDC 7 /* max decorations */ + +#define BASE_LEN 1536 /* basic note length (semibreve or whole note - same as MIDI) */ + +/* accidentals */ +enum accidentals { + A_NULL, /* none */ + A_SH, /* sharp */ + A_NT, /* natural */ + A_FT, /* flat */ + A_DS, /* double sharp */ + A_DF /* double flat */ +}; + +/* bar types - 4 bits per symbol */ +#define B_BAR 1 /* | */ +#define B_OBRA 2 /* [ */ +#define B_CBRA 3 /* ] */ +#define B_COL 4 /* : */ + +/* note structure */ +struct deco { /* describes decorations */ + char n; /* number of decorations */ + unsigned char t[MAXDC]; /* decoration type */ +}; + +struct Note { /* note or rest */ + signed char pits[MAXHD]; /* pitches for notes */ + short lens[MAXHD]; /* note lengths as multiple of BASE */ + char accs[MAXHD]; /* code for accidentals */ + char sl1[MAXHD]; /* which slur starts on this head */ + char sl2[MAXHD]; /* which slur ends on this head */ + char ti1[MAXHD]; /* flag to start tie here */ + char ti2[MAXHD]; /* flag to end tie here */ + short len; /* note length (shortest in chords) */ + unsigned invis:1; /* invisible rest */ + unsigned word_end:1; /* 1 if word ends here */ + unsigned stemless:1; /* note with no stem */ + unsigned lyric_start:1; /* may start a lyric here */ + unsigned grace:1; /* grace note */ + unsigned sappo:1; /* short appoggiatura */ + char nhd; /* number of notes in chord - 1 */ + char p_plet, q_plet, r_plet; /* data for n-plets */ + char slur_st; /* how many slurs start here */ + char slur_end; /* how many slurs end here */ + signed char brhythm; /* broken rhythm */ + struct deco dc; /* decorations */ +}; + +struct clef_s { /* clef */ + char type; +#define TREBLE 0 +#define ALTO 1 +#define BASS 2 +#define PERC 3 + char line; + signed char octave; + signed char transpose; + char invis; +#ifndef CLEF_TRANSPOSE + char check_pitch; /* check if old abc2ps transposition */ +#endif +}; +struct key_s { /* K: info */ + signed char sf; /* sharp (> 0) flats (< 0) */ + char bagpipe; /* HP or Hp */ + char minor; /* major (0) / minor (1) */ + char empty; /* clef alone if 1 */ + char nacc; /* explicit accidentals */ + char pits[8]; + char accs[8]; +}; +struct meter_s { /* M: info */ + short wmeasure; /* duration of a measure */ + short nmeter; /* number of meter elements */ +#define MAX_MEASURE 6 + struct { + char top[8]; /* top value */ + char bot[2]; /* bottom value */ + } meter[MAX_MEASURE]; + }; +struct staff_s { /* %%staves */ + char voice; + char flags; +#define OPEN_BRACE 0x01 +#define CLOSE_BRACE 0x02 +#define OPEN_BRACKET 0x04 +#define CLOSE_BRACKET 0x08 +#define OPEN_PARENTH 0x10 +#define CLOSE_PARENTH 0x20 +#define STOP_BAR 0x40 + char *name; +}; +enum { + ABC_T_NULL, + ABC_T_INFO, /* (text[0] gives the info type) */ + ABC_T_PSCOM, + ABC_T_CLEF, + ABC_T_NOTE, + ABC_T_REST, + ABC_T_BAR, + ABC_T_EOLN, + ABC_T_INFO2, /* (info without header - H:) */ + ABC_T_MREST, /* multi-measure rest */ + ABC_T_MREP, /* measure repeat */ + ABC_T_V_OVER /* voice overlay */ +}; + +enum { + ABC_S_GLOBAL, /* global */ + ABC_S_HEAD, /* in header (after X:) */ + ABC_S_TUNE, /* in tune (after K:) */ + ABC_S_EMBED /* embedded header (between [..]) */ +}; + +enum { + V_OVER_S, /* single & */ + V_OVER_D, /* && */ + V_OVER_SS, /* (& */ + V_OVER_SD, /* (&& */ + V_OVER_E /* )& */ +}; + +/* symbol definition */ +struct abctune; +struct abcsym { + struct abctune *tune; /* tune */ + struct SYMBOL *sym_next; /* next symbol */ + struct SYMBOL *sym_prev; /* previous symbol */ + char sym_type; /* symbol type */ + char state; /* symbol state in file/tune */ + short linenum; /* line number / ABC file */ + char *text; /* main text (INFO, PSCOM), + * guitar chord (NOTE, REST, BAR) */ + char *comment; /* comment part (when keep_comment) */ + union { /* type dependent part */ + struct key_s key; + struct { /* L: info */ + int base_length; /* basic note length */ + } length; + struct meter_s meter; + struct { /* Q: info */ + char *str1; /* string before */ + short length[4]; /* up to 4 note lengths */ + short value; /* tempo value */ + char *str2; /* string after */ + } tempo; + struct { /* V: info */ + char *name; /* name */ + char *fname; /* full name */ + char *nname; /* nick name */ + char voice; /* voice number */ + char merge; /* merge with previous voice */ + signed char stem; /* have all stems up or down */ + } voice; + struct { /* bar, mrest or mrep */ + struct deco dc; /* decorations */ + int type; + char repeat_bar; + char len; /* len if mrest or mrep */ + } bar; + struct clef_s clef; + struct Note note; /* note, rest */ + struct { /* user defined accent */ + char symbol; + char value; + } user; + struct staff_s staves[MAXVOICE]; + struct { /* voice overlay */ + char type; + char voice; + } v_over; + } un; +}; + +/* tune definition */ +struct abctune { + struct abctune *next; /* next tune */ + struct abctune *prev; /* previous tune */ + struct SYMBOL *first_sym; /* first symbol */ + struct SYMBOL *last_sym; /* last symbol */ + int client_data; /* client data */ +}; + +#ifdef WIN32 +#define strcasecmp stricmp +#define strncasecmp strnicmp +#endif + +extern char *deco_tb[]; +extern int severity; +void abc_delete(struct SYMBOL *as); +void abc_free(struct abctune *first_tune); +void abc_insert(char *file_api, + struct SYMBOL *s); +struct SYMBOL *abc_new(struct abctune *t, + char *p, + char *comment); +struct abctune *abc_parse(char *file_api); +char *get_str(char *d, + char *s, + int maxlen); +void note_sort(struct SYMBOL *s); +char *parse_deco(char *p, + struct deco *deco); +bool abc_init(int); diff --git a/src-abcm2ps/buffer.cpp b/src-abcm2ps/buffer.cpp new file mode 100644 index 0000000..80a2729 --- /dev/null +++ b/src-abcm2ps/buffer.cpp @@ -0,0 +1,564 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +#define BUFFLN 100 /* max number of lines in output buffer */ + +static int ln_num; /* number of lines in buffer */ +static float ln_pos[BUFFLN]; /* vertical positions of buffered lines */ +static int ln_buf[BUFFLN]; /* buffer location of buffered lines */ +static float ln_lmarg[BUFFLN]; /* left margin of buffered lines */ +static float ln_scale[BUFFLN]; /* scale of buffered lines */ +static char buf[BUFFSZ]; /* output buffer.. should hold one tune */ +static float cur_lmarg = 0; /* current left margin */ +static float cur_scale = 1.0; /* current scale */ +static float posy; /* vertical position on page */ +static float bposy; /* current position in buffered data */ +static int nepsf; /* counter for epsf output files */ +static int nbpages; /* number of pages in the output file */ + +char *mbf; /* where to PUTx() */ +char *outfnam; +int nbuf; /* number of bytes buffered */ +int use_buffer; /* 1 if lines are being accumulated */ + +/* subroutines for postscript output */ + +/* -- initialize the postscript file -- */ +static void init_ps(const char *str, + int is_epsf) +{ + time_t ltime; + char tstr[41]; + int enc, i; + + if (is_epsf) { +/*fixme: no landscape for EPS?*/ + fprintf(fout, "%%!PS-Adobe-3.0 EPSF-3.0\n" + "%%%%BoundingBox: 0 0 %.0f %.0f\n", + cfmt.pagewidth - cfmt.leftmargin + - cfmt.rightmargin + 20, + -bposy); + cur_lmarg = cfmt.leftmargin - 10; + } else fprintf(fout, "%%!PS-Adobe-3.0\n"); + + /* Title */ + fprintf(fout, "%%%%Title: %s\n", str); + + /* CreationDate */ + time(<ime); + strcpy(tstr, ctime(<ime)); +/* Wed Jun 30 21:49:08 1993\n */ + tstr[10]='\0'; + tstr[16]='\0'; + tstr[24]='\0'; + fprintf(fout, "%%%%Creator: abcm2ps\n" + "%%%%CreationDate: %s, %s %s\n", &tstr[4], &tstr[20], &tstr[11]); + if (!is_epsf) + fprintf(fout, "%%%%Pages: (atend)\n"); + fprintf(fout, "%%%%LanguageLevel: %d\n" + "%%%%EndComments\n" + "%%CommandLine:", + PS_LEVEL); + for (i = 1; i < s_argc; i++) { + fprintf(fout, + strchr(s_argv[i], ' ') != 0 ? " \'%s\'" : " %s", + s_argv[i]); + } + fprintf(fout, "\n\n"); + + if (is_epsf) + fprintf(fout, + "gsave /origstate save def mark\n100 dict begin\n\n"); + + fprintf(fout, "%%%%BeginSetup\n" + "/!{bind def}bind def\n" + "/bdef{bind def}!\n" /* for compatibility */ + "/T/translate load def\n" + "/M/moveto load def\n" + "/RM/rmoveto load def\n" + "/RL/rlineto load def\n" + "/RC/rcurveto load def\n" + "/dlw{0.7 setlinewidth}!\n" +#if PS_LEVEL==1 + "/selectfont{exch findfont exch dup %% emulate level 2 op\n" + " type /arraytype eq {makefont}{scalefont} ifelse setfont}!\n" +#endif + ); + define_encoding(cfmt.encoding); + if ((enc = cfmt.encoding) == 0) + enc = 1; + fprintf(fout, "/mkfontext{\n" + " findfont dup length dict begin\n" + " {1 index /FID ne {def}{pop pop} ifelse} forall\n" + " /Encoding ISOLatin%dEncoding def\n" + " currentdict\n" + " end\n" + " definefont pop}!\n", + enc); + define_fonts(); + define_symbols(); + fprintf(fout, "0 setlinecap 0 setlinejoin\n"); + + write_user_ps(); + fprintf(fout, "%%%%EndSetup\n"); + file_initialized = 1; +} + +/* -- close_page -- */ +static void close_page(void) +{ + if (!in_page) + return; + in_page = 0; + + fprintf(fout, "%%%%PageTrailer\n" + "grestore\n" + "showpage\n"); + cur_lmarg = 0; + cur_scale = 1.0; +} + +/* -- close_output_file -- */ +void close_output_file(bool report) +{ + long m; + + if (fout == 0) + return; + if (tunenum == 0) + alert("No tunes written to output file"); + close_page(); + fprintf(fout, "%%%%Trailer\n" + "%%%%Pages: %d\n" + "%%EOF\n", nbpages); + m = ftell(fout); + fclose(fout); + if (report) + alert( "Output written on %s (%d page%s, %ld bytes)", + outfnam, nbpages, nbpages == 1 ? "" : "s", m); + fout = 0; + file_initialized = 0; + nbpages = tunenum = 0; +} + +/* -- output a header/footer element -- */ +static void format_hf(const char *p) +{ + char *q; + char s[256]; + time_t ltime; + + for (;;) { + if (*p == '\0') + break; + if ((q = (char*)strchr(p, '$')) != 0) + *q = '\0'; + fprintf(fout, "%s", p); + if (q == 0) + break; + p = q + 1; + switch (*p) { + case 'D': + time(<ime); + strcpy(s, ctime(<ime)); +/* Wed Jun 30 21:49:08 1993\n */ + s[10]='\0'; + s[16]='\0'; + s[24]='\0'; + fprintf(fout, "%s, %s %s", &s[4], &s[20], &s[11]); + break; + case 'F': /* ABC file name */ + fprintf(fout, "%s", in_fname); + break; + case 'P': /* page number */ + if (p[1] == '0') { + p++; + if (pagenum & 1) + break; + } else if (p[1] == '1') { + p++; + if ((pagenum & 1) == 0) + break; + } + fprintf(fout, "%d", pagenum); + break; + case 'T': /* tune title */ + tex_str(s, info.title[0], sizeof s, 0); + fprintf(fout, "%s", s); + break; + case 'V': + fprintf(fout,"abcm2ps"); + break; + default: + continue; + } + p++; + } +} + +/* -- output the header or footer -- */ +static float headfooter(int header, + float pwidth, + float pheight) +{ + char str[256]; + const char *p; + char *q, *r; + float size, y, wsize; + int fnum; + + if (header) { + p = cfmt.header; + size = cfmt.headerfont.size; + fnum = cfmt.headerfont.fnum; + y = 2.; + wsize = 0; + } else { + p = cfmt.footer; + size = cfmt.footerfont.size; + fnum = cfmt.footerfont.fnum; + y = - (pheight - cfmt.topmargin - cfmt.botmargin) + - size + 2.; + wsize = 0; + } + + fprintf(fout, "%.1f F%d ", size, fnum); + + /* may have 2 lines */ + if ((r = (char*)strstr(p, "\\n")) != 0) { + if (!header) + y += size; + wsize += size; + *r = '\0'; + } + + for (;;) { + tex_str(str, p, sizeof str, 0); + + /* left side */ + p = str; + if ((q = (char*)strchr(p, '\t')) != 0) { + if (q != p) { + *q = '\0'; + fprintf(fout, "%.1f %.1f M (", + cfmt.leftmargin, y); + format_hf(p); + fprintf(fout, ") show\n"); + } + p = q + 1; + } + if ((q = (char*)strchr(p, '\t')) != 0) + *q = '\0'; + if (q != p) { + fprintf(fout, "%.1f %.1f M (", + pwidth * 0.5, y); + format_hf(p); + fprintf(fout, ") cshow\n"); + } + if (q != 0) { + p = q + 1; + if (*p != '\0') { + fprintf(fout, "%.1f %.1f M (", + pwidth - cfmt.rightmargin, y); + format_hf(p); + fprintf(fout, ") lshow\n"); + } + } + if (r == 0) + break; + *r = '\\'; + p = r + 2; + r = 0; + y -= size; + } + + return wsize; +} + +/* -- initialize postscript page -- */ +static void init_page(void) +{ + float pheight, pwidth; + + if (in_page) + return; + if (!file_initialized) + init_ps(in_fname, 0); + in_page = 1; + nbpages++; + + fprintf(fout, "%%%%Page: %d %d\n", + nbpages, nbpages); + if (cfmt.landscape) { + pheight = cfmt.pagewidth; + pwidth = cfmt.pageheight; + fprintf(fout, "%%%%PageOrientation: Landscape\n" + "gsave 90 rotate 0 %.1f T\n", + -cfmt.topmargin); + } else { + pheight = cfmt.pageheight; + pwidth = cfmt.pagewidth; + fprintf(fout, "gsave 0 %.1f T\n", + pheight - cfmt.topmargin); + } + + posy = pheight - cfmt.topmargin - cfmt.botmargin; + + /* output the header and footer */ + if (cfmt.header == 0 && pagenumbers != 0) { + switch (pagenumbers) { + case 1: cfmt.header = "$P\t"; break; + case 2: cfmt.header = "\t\t$P"; break; + case 3: cfmt.header = "$P0\t\t$P1"; break; + case 4: cfmt.header = "$P1\t\t$P0"; break; + } + } + if (cfmt.header != 0) { + float dy; + + dy = headfooter(1, pwidth, pheight); + if (dy != 0) { + fprintf(fout, "0 %.1f T\n", -dy); + posy -= dy; + } + } + if (cfmt.footer != 0) + posy -= headfooter(0, pwidth, pheight); + pagenum++; +} + +/* -- open the output file -- */ +void open_output_file(const char *out_file) +{ + if ((fout = fopen(out_file, "w")) == 0) { + alert( "Cannot create output file %s", out_file); + } + outfnam = const_cast(out_file); +} + +/* -- epsf_title -- */ +static void epsf_title(char *p) +{ + char c; + + while ((c = *p) != '\0') { + if (c == ' ') + *p = '_'; + else if (c == DIRSEP || (unsigned) c >= 127) + *p = '.'; + p++; + } +} + +/* -- output the EPS file -- */ +void write_eps(void) +{ + char fnm[80], finf[80]; + + close_output_file(true); + if (choose_outname) { + strncpy(fnm, info.title[0], sizeof fnm - 4); + fnm[sizeof fnm - 5] = '\0'; + epsf_title(fnm); + strcat(fnm, ".eps"); + } else sprintf(fnm, "%.70s%03d.eps", "out", ++nepsf); + sprintf(finf, "%.72s (%.4s)", in_fname, info.xref); + if ((fout = fopen(fnm, "w")) == 0) { + alert( "Cannot open output file %s", fnm); + return; + } + init_ps(finf, 1); + fprintf(fout, "0 %.1f T\n", -bposy); + write_buffer(); + fprintf(fout, "showpage\nend\n" + "cleartomark origstate restore grestore\n"); + fclose(fout); + fout = 0; + cur_lmarg = 0; + cur_scale = 1.0; +} + +/* -- write_pagebreak -- */ +void write_pagebreak() +{ + close_page(); + init_page(); + if (page_init[0] != '\0') + fprintf(fout, "%s\n", page_init); +} + +/* subroutines to handle output buffer */ + +/* -- update the output buffer pointer -- */ +/* called from the PUTx() macros */ +void a2b(void) +{ + int l; + + if (!in_page && !epsf) + init_page(); + + l = strlen(mbf); + + nbuf += l; + if (nbuf >= BUFFSZ - 500) { /* must have place for 1 more line */ + alert( "a2b: buffer full, BUFFSZ=%d", BUFFSZ); + return; + } + + mbf += l; +} + +/* -- translate down by 'h' absolute points in output buffer -- */ +void abskip(float h) +{ + PUT1("0 %.2f T\n", -h / cfmt.scale); + bposy -= h; +} + +/* -- translate down by 'h' scaled points in output buffer -- */ +void bskip(float h) +{ + PUT1("0 %.2f T\n", -h); + bposy -= h * cfmt.scale; +} + +/* -- clear_buffer -- */ +void clear_buffer(void) +{ + nbuf = 0; + bposy = 0.0; + ln_num = 0; + mbf = buf; +} + +/* -- write buffer contents, break at full pages -- */ +void write_buffer(void) +{ + int i, l, b2; + float p1, dp; + + if (nbuf == 0) + return; + + i = 0; + p1 = 0; + for (l = 0; l < ln_num; l++) { + b2 = ln_buf[l]; + dp = ln_pos[l] - p1; + if (posy + dp < 0 && !epsf) + write_pagebreak(); + if (ln_scale[l] != cur_scale) { + fprintf(fout, "%.2f dup scale\n", + ln_scale[l] / cur_scale); + cur_scale = ln_scale[l]; + } + if (ln_lmarg[l] != cur_lmarg) { + fprintf(fout, "%.1f 0 T\n", + (ln_lmarg[l] - cur_lmarg) / cur_scale); + cur_lmarg = ln_lmarg[l]; + } + fwrite(&buf[i], 1, b2 - i, fout); + i = b2; + posy += dp; + p1 = ln_pos[l]; + } + + fwrite(&buf[i], 1, nbuf - i, fout); + + clear_buffer(); +} + +/* -- handle completed block in buffer -- */ +/* if the added stuff does not fit on current page, write it out + after page break and change buffer handling mode to pass though */ +void buffer_eob(void) +{ + if (ln_num >= BUFFLN) { + alert("max number of buffer lines exceeded -- check BUFFLN"); + return; + } + + ln_buf[ln_num] = nbuf; + ln_pos[ln_num] = bposy; + ln_lmarg[ln_num] = cfmt.leftmargin; + ln_scale[ln_num] = cfmt.scale; + ln_num++; + + if (!use_buffer) { + write_buffer(); + return; + } + + if ((posy + bposy < 0 + || cfmt.oneperpage) + && !epsf) { + if (tunenum != 1) + write_pagebreak(); + write_buffer(); + use_buffer = 0; + } +} + +/* -- dump buffer if less than nb bytes available -- */ +void check_buffer(void) +{ + if (nbuf + BUFFSZ1 > BUFFSZ) { + alert("Possibly bad page breaks, BUFFSZ exceeded"); + write_buffer(); + use_buffer = 0; + } +} + +/* -- return the current vertical offset in the page -- */ +float get_bposy(void) +{ + return posy + bposy; +} + +/* -- set value in the output buffer -- */ +/* The values are flagged as "\x01vnnn.nn" where + * - 'v' is the variable index ('0'..'z') + * - 'nnn.nn' is the value to add to the variable + * The variables are: + * '0'..'O': staff offsets */ +/* this function must be called before buffer_eob() */ +void set_buffer(float *p_v) +{ + char *p; + int i; + float v; + + if (ln_num == 0) + p = buf; + else p = &buf[ln_buf[ln_num - 1]]; + while ((p = strchr(p, '\x01')) != 0) { + i = p[1] - '0'; + sscanf(p + 2, "%f", &v); + p += sprintf(p, "%7.1f", p_v[i] + v); + *p = ' '; + } +} diff --git a/src-abcm2ps/deco.cpp b/src-abcm2ps/deco.cpp new file mode 100644 index 0000000..982f0a9 --- /dev/null +++ b/src-abcm2ps/deco.cpp @@ -0,0 +1,1678 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +int nbar; /* current measure number */ +int nbar_rep; /* last repeat bar number */ + +static struct deco_elt { + deco_elt *next; /* next decoration */ + SYMBOL *s; /* symbol */ + uchar t; /* decoration index */ + uchar staff; /* staff */ + char inv; /* invert the glyph if 1 */ + char flags; +#define DE_VAL 0x01 /* put extra value if 1 */ +#define DE_UP 0x02 /* above the staff */ +#define DE_TREATED 0x04 /* (for !decox(! - !decox)!) */ +#define DE_BELOW 0x08 /* below the staff */ + float x, y; /* x, y */ + float v; /* extra value */ + const char *str; /* string / 0 */ +} *deco_head, *deco_tail; + +struct deco_def_s; +typedef void draw_f(deco_elt *de); +static draw_f d_arp, d_cresc, d_near, d_slide, d_upstaff, + d_pf, d_trill; + +/* decoration table */ +/* !! don't change the order of the numbered items !! */ +static struct deco_def_s { + const char *name; + uchar func; /* function index */ + signed char ps_func; /* postscript function index */ + char h; /* height */ + char wl; /* width */ + char wr; + uchar str; /* string index - 127=deco name */ +} deco_def_tb[128] = { + {0, 0, 0, 0}, /* 0: unknown */ + {"dot", 0, 0, 4}, /* 1 */ + {"roll", 3, 10, 10}, /* 2 */ + {"fermata", 3, 6, 12}, /* 3 */ + {"emphasis", 3, 21, 8}, /* 4 */ + {"lowermordent", 3, 5, 10}, /* 5 */ + {"coda", 3, 16, 22}, /* 6 */ + {"uppermordent", 3, 4, 10}, /* 7 */ + {"segno", 3, 17, 20}, /* 8 */ + {"trill", 3, 7, 11}, /* 9 */ + {"upbow", 3, 8, 10}, /* 10 */ + {"downbow", 3, 9, 9}, /* 11 */ + {"gmark", 3, 3, 6}, /* 12 */ + {"slide", 1, 2, 3, 7}, /* 13 */ + {"tenuto", 0, 1, 4}, /* 14 */ + {"crescendo(", 7, -1, 20}, /* 15 */ + {"crescendo)", 7, 19, 20}, /* 16 */ + {"diminuendo(", 7, -1, 20}, /* 17 */ + {"diminuendo)", 7, 19, 20}, /* 18 */ + {"breath", 3, 18, 0}, /* 19 */ + {"longphrase", 3, 24, 0}, /* 20 */ + {"mediumphrase", 3, 25, 0}, /* 21 */ + {"shortphrase", 3, 26, 0}, /* 22 */ + {"invertedfermata", 3, 6, 12}, /* 23 */ + {"arpeggio", 2, 27, 0, 10}, /* 24 */ + {"invertedturn", 3, 22, 10}, /* 25 */ + {"invertedturnx", 3, 23, 10}, /* 26 */ + {"0", 3, 11, 8, 0, 0, 127}, + {"1", 3, 11, 8, 0, 0, 127}, + {"2", 3, 11, 8, 0, 0, 127}, + {"3", 3, 11, 8, 0, 0, 127}, + {"4", 3, 11, 8, 0, 0, 127}, + {"5", 3, 11, 8, 0, 0, 127}, + {"+", 3, 20, 7}, + {"accent", 3, 21, 8}, + {"D.C.", 3, 12, 16, 0, 0, 127}, + {"D.S.", 3, 12, 16, 0, 0, 127}, + {"emphasis", 3, 21, 8}, + {"fine", 3, 12, 16, 0, 0, 1}, + {"f", 6, 13, 20, 2, 2}, + {"ff", 6, 13, 20, 2, 5}, + {"fff", 6, 13, 20, 2, 8}, + {"ffff", 6, 13, 20, 2, 11}, + {"mf", 6, 13, 20, 2, 5}, + {"mordent", 3, 5, 10}, + {"open", 3, 30, 10}, + {"p", 6, 13, 20, 2, 2}, + {"pp", 6, 13, 20, 2, 5}, + {"ppp", 6, 13, 20, 2, 8}, + {"pppp", 6, 13, 20, 2, 11}, + {"pralltriller", 3, 4, 10}, + {"sfz", 6, 14, 20, 2, 8}, + {"turn", 3, 22, 10}, + {"wedge", 3, 29, 8}, + {"turnx", 3, 23, 10}, + {"trill(", 5, -1, 8}, + {"trill)", 5, 28, 8}, + {"snap", 3, 31, 14}, + {"thumb", 3, 15, 14}, + {0, 0, 0, 0} +}; + +/* c function table */ +static draw_f *func_tb[8] = { + d_near, /* 0 - near the note */ + d_slide, /* 1 */ + d_arp, /* 2 */ + d_upstaff, /* 3 - tied to note */ + d_upstaff, /* 4 (below the staff) */ + d_trill, /* 5 */ + d_pf, /* 6 - tied to staff */ + d_cresc /* 7 */ +}; + +/* postscript function table */ +static const char *ps_func_tb[64] = { + "stc", /* 0: dot */ + "emb", /* 1: tenuto */ + "sld", /* 2: slide */ + "grm", /* 3: gracing mark */ + "umrd", /* 4: uppermordent */ + "lmrd", /* 5: lowermordent */ + "hld", /* 6: fermata */ + "trl", /* 7: trill */ + "upb", /* 8: upbow */ + "dnb", /* 9: downbow */ + "cpu", /* 10: roll */ + "fng", /* 11: fingers */ + "dacs", /* 12: D.C./ D.S. */ + "pf", /* 13: p, f, pp, .. */ + "sfz", /* 14: sfz */ + "thumb", /* 15: thumb */ + "coda", /* 16: coda */ + "sgno", /* 17: segno */ + "brth", /* 18: breath */ + "cresc", /* 19: (de)crescendo */ + "dplus", /* 20: plus */ + "accent", /* 21: accent */ + "turn", /* 22: turn */ + "turnx", /* 23: turn with bar */ + "lphr", /* 24: longphrase */ + "mphr", /* 25: mediumphrase */ + "sphr", /* 26: shortphrase */ + "arp", /* 27: arpeggio */ + "ltr", /* 28: long trill */ + "wedge", /* 29: wedge */ + "opend", /* 30: open */ + "snap", /* 31: snap */ +}; + +static const char *str_tb[32] = { + 0, + "FINE", /* 1 */ +}; + +static SYMBOL *first_note; /* first note/rest of the line */ + +static void draw_gchord(SYMBOL *s, float gchy); + +/* get the max/min vertical offset */ +static float get_y(SYMBOL *s, + int up, + float x, + float w, + float h) +{ + SYMBOL *s2, *s_start; + int staff, start_seen; + float y; + + s_start = s; + start_seen = 0; + staff = s->staff; + y = up ? s->dc_top : s->dc_bot; + while (s->ts_prev != 0 && s->ts_prev->x >= x) + s = s->ts_prev; + x += w; /* right offset */ + if (up) { + for (s2 = s; s2 != 0; s2 = s2->ts_next) { + if (start_seen) { + if (s2->x > x) + break; + } else if (s2 == s_start) + start_seen = 1; + if (s2->staff == staff) { + if (y < s2->dc_top) + y = s2->dc_top; + } + } + } else { + for (s2 = s; s2 != 0; s2 = s2->ts_next) { + if (start_seen) { + if (s2->x > x) + break; + } else if (s2 == s_start) + start_seen = 1; + if (s2->staff == staff) { + if (y > s2->dc_bot - h) + y = s2->dc_bot - h; + } + } + } + return y; +} + +/* adjust the vertical offsets */ +static void set_y(SYMBOL *s, + int up, + float x, + float w, + float y) +{ + SYMBOL *s_start; + int staff, start_seen; + + s_start = s; + start_seen = 0; + staff = s->staff; + while (s->ts_prev != 0 && s->ts_prev->x >= x) + s = s->ts_prev; + x += w; /* right offset */ + if (up) { + for (; s != 0; s = s->ts_next) { + if (start_seen) { + if (s->x > x) + break; + } else if (s == s_start) + start_seen = 1; + if (s->staff == staff + && s->dc_top < y) + s->dc_top = y; + } + } else { + for (; s != 0; s = s->ts_next) { + if (start_seen) { + if (s->x > x) + break; + } else if (s == s_start) + start_seen = 1; + if (s->staff == staff + && s->dc_bot > y) + s->dc_bot = y; + } + } +} + +/* -- drawing functions -- */ +/* special case for arpeggio */ +static void d_arp(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + int m, h; + float yc, xc; + + s = de->s; + dd = &deco_def_tb[de->t]; + xc = 10; + for (m = 0; m <= s->nhd; m++) { + float dx; + + if (s->un.note.accs[m]) + dx = 8 - s->shhd[m] + s->shac[m]; + else { + dx = 10 - s->shhd[m]; + switch (s->head) { + case H_SQUARE: + case H_OVAL: + dx += 2.5; + break; + } + } + if (dx > xc) + xc = dx; + } + h = s->ymx - s->ymn; + if (h < 18) + h = 18; + yc = (float) (s->yav - h / 2 - 8); + + de->flags = DE_VAL; + de->v = h; + de->x = s->x - xc; + de->y = yc; +} + +/* special case for (de)crescendo */ +static void d_cresc(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + int staff, voice, up; + float x, y, dx; + SYMBOL *s2; + + if (de->flags & DE_TREATED) + return; + s = de->s; + dd = &deco_def_tb[de->t]; + voice = s->voice; + + if (dd->ps_func < 0) { /* start of (de)crescendo */ + int t; + + /* !! works when '(de)crescendo)' is '(de)crescendo(' + 1 !! */ + t = de->t + 1; + for (de = de->next; de != 0; de = de->next) + if (de->t == t && de->s->voice == voice) + break; + if (de == 0) { /* no end, insert one */ + de = (deco_elt *) malloc(sizeof *de); + memset(de, 0, sizeof *de); + deco_tail->next = de; + deco_tail = de; + de->s = s; + de->t = t; + } + s2 = de->s; + x = s->x + 4.; + if (s->type == NOTE + && s->un.note.dc.n > 1) + x += 6.; + } else { /* end without start */ + s2 = s; + s = first_note; + x = s->x - s->wl - 4.; + } + de->staff = staff = s2->staff; + +#if 1 +/*fixme: test*/ + if (s2->multi != 0) + up = s2->multi > 0 ? 1 : 0; + else +#endif + if (cfmt.exprabove + || (!cfmt.exprbelow + && staff_tb[staff].nvocal != 0)) + up = 1; + else up = 0; + + if (s2 == s + && dd->ps_func < 0) { /* if no decoration end */ + dx = realwidth - x - 6.; + if (dx < 20.) { + x = realwidth - 20. - 6.; + dx = 20.; + } + } else { + dx = s2->x - x - 4.; + if (s2->type == NOTE + && s2->un.note.dc.n > 1) + dx -= 6.; + if (dx < 20.) + dx = 20.; + } + + y = get_y(s, up, x, dx, dd->h); + + if (de->t == 16) { /* 'crescendo)' */ + x += dx; + dx = -dx; + } + + de->flags = DE_VAL|DE_TREATED; + if (up) + de->flags |= DE_UP; + de->v = dx; + de->x = x; + de->y = y; + /* (set_y is done in draw_deco_staff) */ +} + +/* near the note (dot, tenuto) */ +static void d_near(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + int y, sig; + + s = de->s; + dd = &deco_def_tb[de->t]; + sig = s->stem > 0 ? -1 : 1; + if (s->multi) + sig = -sig; + if (sig > 0) + y = (int)s->dc_top; + else y = (int)s->dc_bot; + y += 3 * sig; + if (y > -3 && y < 24 + 3) { + if (sig > 0) + y++; + y = (y + 5) / 6 * 6 - 3; /* between lines */ + } + if (s->dc_top < y + 2) + s->dc_top = y + 2; + if (s->dc_bot > y - 2) + s->dc_bot = y - 2; + + de->x = s->x + s->shhd[0]; + de->y = y; +} + +/* special case for piano/forte indications */ +static void d_pf(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + float y, w; + const char *str; + int up; + + s = de->s; + dd = &deco_def_tb[de->t]; +#if 1 +/*fixme: test*/ + if (s->multi != 0) + up = s->multi > 0 ? 1 : 0; + else +#endif + if (cfmt.exprabove + || (!cfmt.exprbelow + && staff_tb[s->staff].nvocal != 0)) + up = 1; + else up = 0; + + str = dd->name; + if (dd->str != 0 && dd->str != 127) + str = str_tb[dd->str]; + w = dd->wl + dd->wr; + y = get_y(s, up, s->x - dd->wl, w, dd->h); + + if (up) + de->flags = DE_UP; + de->v = w; + de->x = s->x; + de->y = y; + de->str = str; + /* (set_y is done in draw_deco_staff) */ +} + +/* special case for slide */ +static void d_slide(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + int m; + float yc, xc; + + s = de->s; + dd = &deco_def_tb[de->t]; + yc = s->ymn; + xc = 5; + for (m = 0; m <= s->nhd; m++) { + float dx, dy; + + if (s->un.note.accs[m]) + dx = 4 - s->shhd[m] + s->shac[m]; + else { + dx = 5 - s->shhd[m]; + switch (s->head) { + case H_SQUARE: + case H_OVAL: + dx += 2.5; + break; + } + } + dy = (float) (3 * (s->pits[m] - 18)) - yc; + if (dy < 10 && dx > xc) + xc = dx; + } + de->x = s->x - xc; + de->y = yc; +} + +/* special case for long trill */ +static void d_trill(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + int staff, voice, up; + float x, y, dx; + SYMBOL *s2; + + if (de->flags & DE_TREATED) + return; + s = de->s; + dd = &deco_def_tb[de->t]; + voice = s->voice; + if (dd->ps_func < 0) { /* start of trill */ + int t; + + /* !! works when 'trill)' is 'trill(' + 1 !! */ + t = de->t + 1; + for (de = de->next; de != 0; de = de->next) + if (de->t == t && de->s->voice == voice) + break; + if (de == 0) { /* no end, insert one */ + de = (deco_elt *) malloc(sizeof *de); + memset(de, 0, sizeof *de); + deco_tail->next = de; + deco_tail = de; + de->s = s; + de->t = t; + } + s2 = de->s; + x = s->x; + if (s->type == NOTE + && s->un.note.dc.n > 1) + x += 6.; + } else { /* end without start */ + s2 = s; + s = first_note; + x = s->x - s->wl - 4.; + } + de->staff = staff = s2->staff; + + up = s2->multi >= 0; + if (s2 == s) { /* if no decoration end */ + dx = realwidth - x - 6.; + if (dx < 20.) { + x = realwidth - 20. - 6.; + dx = 20.; + } + } else { + dx = s2->x - x - 6.; + if (s2->type == NOTE) + dx -= 6.; + if (dx < 20.) + dx = 20.; + } + + y = get_y(s, up, x, dx, dd->h); + if (up) { + if (y < 24. + 2.) + y = 24. + 2.; + } else { + if (y > -2.) + y = -2.; + } + + de->flags = DE_VAL|DE_TREATED; + de->v = dx; + de->x = x; + de->y = y; + + if (up) + y += dd->h; + set_y(s, up, x, dx, y); +} + +/* above (or below) the staff */ +static void d_upstaff(deco_elt *de) +{ + SYMBOL *s; + deco_def_s *dd; + float x, yc; + int inv; + const char *str; + + s = de->s; + dd = &deco_def_tb[de->t]; + inv = 0; + x = s->x + s->shhd[0]; + if (dd->str != 0) + if (dd->str == 127) + str = dd->name; + else str = str_tb[dd->str]; + else str = 0; + switch (de->t) { + case 2: /* roll */ + if (s->multi < 0 + || (s->multi == 0 && s->stem > 0)) { + yc = s->dc_bot; + if (yc > -2.) + yc = -2.; + yc -= dd->h; + s->dc_bot = yc; + inv = 1; + } else { + yc = s->dc_top + 3; + if (yc < 24. + 2.) + yc = 24. + 2.; + if (s->stem <= 0 + && (s->dots == 0 || ((int) s->y % 6))) + yc -= 2; + s->dc_top = yc + dd->h; + } + break; + case 19: /* breath */ + case 20: /* longphrase */ + case 21: /* mediumphrase */ + case 22: /* shortphrase */ + yc = 24. + 3.; + if (s->nxt != 0) + x += (s->nxt->x - x) * 0.4; + else x += 10; + break; + case 25: /* invertedturn */ + case 26: /* invertedturnx */ + inv = 1; + /* fall thru */ + default: + if (s->multi >= 0 + && de->t != 23 /* invertedfermata */ + && !(de->flags & DE_BELOW)) { + yc = s->dc_top; + if (yc < 24. + 2.) + yc = 24. + 2.; + s->dc_top = yc + dd->h; + } else { + yc = s->dc_bot; + if (yc > -2.) + yc = -2.; + yc -= dd->h; + s->dc_bot = yc; + switch (de->t) { + case 3: /* fermata */ + case 23: /* invertedfermata */ + inv = 1; + break; + } + } + break; + } + if (inv) + yc += dd->h; + + de->inv = inv; + de->str = str; + de->x = x; + de->y = yc; +} + +/* -- add a decoration - from %%deco -- */ +/* syntax: + * %%deco [] + */ +void deco_add(char *text) +{ + deco_def_s *dd; + int deco; + char name[16]; + int c_func; + char ps_func[16]; + int h, wl, wr, n; + int ps_x, str_x; + + /* extract the arguments */ + if (sscanf(text, "%15s %d %15s %d %d %d%n", + name, &c_func, ps_func, &h, &wl, &wr, &n) != 6) { + alert( "Invalid deco %s", text); + return; + } + if (c_func < 0 || c_func >= sizeof func_tb / sizeof func_tb[0]) { + alert( "%%%%deco: bad C function index (%s)", text); + return; + } + if (h < 0 || wl < 0 || wr < 0) { + alert( "%%%%deco: cannot have a negative value (%s)", text); + return; + } + if (h > 50 || wl > 30 || wr > 30) { + alert( "%%%%deco: abnormal h/wl/wr value (%s)", text); + return; + } + text += n; + while (isspace((char) *text)) + text++; + /* search the decoration */ + for (deco = 1, dd = &deco_def_tb[1]; deco < 128; deco++, dd++) { + if (dd->name == 0) { + alert( "Decoration %s unknown",name); + return; + } + if (strcmp(dd->name, name) == 0) + break; + } + + /* search the postscript function */ + for (ps_x = 0; ps_x < sizeof ps_func_tb / sizeof ps_func_tb[0]; ps_x++) { + if (ps_func_tb[ps_x] == 0 + || strcmp(ps_func_tb[ps_x], ps_func) == 0) + break; + } + if (ps_x == sizeof ps_func_tb / sizeof ps_func_tb[0]) { + alert( "Too many postscript functions"); + return; + } + + /* have an index for the string */ + if (*text != '\0') { + for (str_x = 1; str_x < sizeof str_tb / sizeof str_tb[0]; str_x++) { + if (str_tb[str_x] == 0 + || strcmp(str_tb[str_x], text) == 0) + break; + } + if (str_x == sizeof str_tb / sizeof str_tb[0]) { + alert( "Too many decoration strings"); + return; + } + } else str_x = 0; + + /* set the values */ + if (dd->name == 0) + dd->name = strdup(name); /* new decoration */ + dd->func = c_func; + if (ps_func_tb[ps_x] == 0) { + if (ps_func[0] == '-' && ps_func[1] == '\0') + ps_x = -1; + else ps_func_tb[ps_x] = strdup(ps_func); + } + dd->ps_func = ps_x; + dd->h = h; + dd->wl = wl; + dd->wr = wr; + if (str_x != 0 && str_tb[str_x] == 0) { + if (strcmp(text, name) == 0) + str_x = 127; + else str_tb[str_x] = strdup(text); + } + dd->str = str_x; +} + +/* -- convert the decorations -- */ +void deco_cnv(deco *dc, + SYMBOL *s) +{ + int i; + + for (i = dc->n; --i >= 0; ) { + uchar deco; + + deco = dc->t[i]; + if (deco < 128) { + deco = deco_tune[deco]; + if (deco == 0 && dc->t[i] != 0) + alert("Notation '%c' not treated (line %d)", dc->t[i],s->linenum); + } else + deco = deco_intern(deco); + dc->t[i] = deco; + } +} + +/* -- update the x position of a decoration -- */ +void deco_update(SYMBOL *s, float dx) +{ + deco_elt *de; + + for (de = deco_head; de != 0; de = de->next) { + if (de->s == s) + de->x += dx; + } +} + +/* -- convert the external deco number to the internal one -- */ +char deco_intern(uchar deco) +{ + char *name; + name = deco_tb[deco - 128]; + + for (deco = 1; deco < 127; deco++) { + if (deco_def_tb[deco].name == 0) + deco = 127; + else if (strcmp(deco_def_tb[deco].name, name) == 0) + break; + } + + if (deco == 128) { + alert( "Decoration %s not treated", name); + deco = 0; + } + + return deco; +} + +/* -- adjust the symbol width -- */ +float deco_width(SYMBOL *s) +{ + deco *dc; + int i; + float wl; + + wl = 0; + if (s->type == BAR) + dc = &s->un.bar.dc; + else dc = &s->un.note.dc; + for (i = dc->n; --i >= 0; ) { + deco_def_s *dd; + + dd = &deco_def_tb[dc->t[i]]; + switch (dd->func) { + case 1: wl += 7.; break; /* slide */ + case 2: wl += 10.; break; /* arpeggio */ + } + } + return wl; +} + +/* -- draw the decorations -- */ +/* (the staves are defined) */ +void draw_all_deco(void) +{ + deco_elt *de; + + for (de = deco_head; de != 0; de = de->next) { + deco_def_s *dd; + int f; + + dd = &deco_def_tb[de->t]; + if ((f = dd->ps_func) < 0) + continue; + + if (de->flags & DE_VAL) + PUT1("%.1f ", de->v); + if (de->str) + PUT1("(%s) ", de->str); + PUT2("%.1f %1.2f ", + de->x, + de->y + staff_tb[de->staff].y); + if (de->inv) + PUT1("gsave 1 -1 scale neg %s grestore\n", ps_func_tb[f]); + else PUT1("%s\n", ps_func_tb[f]); + } +} + +/* -- draw the decorations near the note (only) -- */ +/* (the staves are not yet defined) */ +/* this function must be called first as it builds the deco element table */ +void draw_deco_near(void) +{ + SYMBOL *s; + int k; + deco *dc; + uchar deco; + deco_def_s *dd; + deco_elt *de; + SYMBOL *first; + + deco_head = deco_tail = 0; + first = 0; + for (s = first_voice->sym; s != 0; s = s->ts_next) { + switch (s->type) { + case BAR: + if (s->un.bar.dc.n == 0) + continue; + dc = &s->un.bar.dc; + break; + case NOTE: + case REST: + case MREST: + if (first == 0) + first = s; + if (s->un.note.dc.n == 0) + continue; + dc = &s->un.note.dc; + break; + default: + continue; + } + + for (k = dc->n; --k >= 0; ) { + deco = dc->t[k]; + if (deco == 0) + continue; + dd = &deco_def_tb[deco]; + + /* memorize the decorations */ + de = (deco_elt *) malloc(sizeof *de); + memset(de, 0, sizeof *de); + if (deco_head == 0) + deco_head = de; + else deco_tail->next = de; + deco_tail = de; + de->s = s; + de->t = deco; + de->staff = s->staff; + + if (dd->func >= 3) /* if not near the note */ + continue; + if (s->type != NOTE) { + alert( "Cannot have a %s on a rest or a bar (line %d)", + dd->name, s->linenum); + continue; + } + func_tb[dd->func](de); + } + } + first_note = first; +} + +/* -- draw more decorations tied to the note -- */ +/* (the staves are not yet defined) */ +void draw_deco_note(void) +{ + deco_elt *de; + + for (de = deco_head; de != 0; de = de->next) { + deco_def_s *dd; + int f; + + dd = &deco_def_tb[de->t]; + f = dd->func; + if (f < 3 || f >= 6) + continue; /* not tied to the note */ + if (f == 4) + de->flags |= DE_BELOW; + func_tb[f](de); + } +} + +/* -- draw the decorations tied to the staff -- */ +/* (the staves are not yet defined) */ +void draw_deco_staff(void) +{ + SYMBOL *s; + VOICE_S *p_voice; + float x, y; + deco_elt *de; + int voice; + int some_gchord; + struct { + float ymin, ymax; + } minmax[MAXSTAFF]; + + /* set the top and bottom of all symbols out of the staves */ + for (s = first_voice->sym; s != 0; s = s->ts_next) { + if (s->dc_top < 24. + 2.) + s->dc_top = 24. + 2.; + if (s->dc_bot > -2.) + s->dc_bot = -2.; + } + + /* search the vertical offset for the guitar chords */ + memset(minmax, 0, sizeof minmax); + some_gchord = 0; + for (s = first_voice->sym; s != 0; s = s->ts_next) { + float w; + char *p; + + if (s->text == 0) + continue; + switch (s->type) { + case NOTE: + case REST: + case MREST: + break; + case BAR: + if (!s->un.bar.repeat_bar) + break; + default: + continue; + } + some_gchord = 1; + w = cwid('a') * cfmt.gchordfont.size * 1.1; + if ((p = strchr(s->text, '\n')) != 0) + w *= p - s->text; + else w *= strlen(s->text); + y = get_y(s, 1, s->x, w, 0); + if (y > minmax[s->staff].ymax) + minmax[s->staff].ymax = y; + } + + /* draw the guitar chords if any */ + if (some_gchord) { + set_font(&cfmt.gchordfont); + for (s = first_voice->sym; s != 0; s = s->ts_next) { + if (s->text == 0) + continue; + switch (s->type) { + case NOTE: + case REST: + case MREST: + break; + case BAR: + if (!s->un.bar.repeat_bar) + break; + default: + continue; + } + draw_gchord(s, minmax[s->staff].ymax + 4); + } + } + + /* compute the max number of lyrics */ + if (!cfmt.musiconly) { + for (s = first_voice->sym; s != 0; s = s->ts_next) { + lyrics *ly; + int nlyric; + + if ((ly = s->ly) == 0) + continue; + for (nlyric = MAXLY; --nlyric >= 0; ) + if (ly->w[nlyric] != 0) + break; + nlyric++; + voice = s->voice; + if (voice_tb[voice].nvocal < nlyric) + voice_tb[voice].nvocal = nlyric; + } + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + staff_tb[p_voice->staff].nvocal += p_voice->nvocal; + } + + /* draw the repeat bars */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + SYMBOL *s1, *s2, *first_repeat; + float y2; + int i, repnl; + + if (p_voice->second + || staff_tb[p_voice->staff].brace_end) + continue; + + /* search the max y offset */ + y = 24. + 6. + 20.; + first_repeat = 0; + for (s = p_voice->sym; s != 0; s = s->nxt) { + if (s->type != BAR + || !s->un.bar.repeat_bar) + continue; +/*fixme: line cut on repeat!*/ + if (s->nxt == 0) + break; + if (first_repeat == 0) { + set_font(&cfmt.repeatfont); + first_repeat = s; + } + s1 = s; + i = 4; + for (;;) { + if (s->nxt == 0) + break; + s = s->nxt; + if (s->type == BAR) { + if (((s->un.bar.type & 0xf0) /* if complex bar */ + && s->un.bar.type != (B_OBRA << 4) + B_CBRA) + || s->un.bar.type == B_CBRA + || s->un.bar.repeat_bar) + break; + if (--i < 0) /*fixme*/ + break; + } + } + y2 = get_y(s1, 1, s1->x, s->x - s1->x, 0); + if (y2 > y) + y = y2; + + /* have room for the repeat numbers */ + y2 = s1->nxt->dc_top; + if (s1->nxt->text != 0) /* if guitar chord */ + y2 -= cfmt.gchordfont.size; /* got already */ + if (y2 > 24. + 6. + 4.) + y = y2 + 10. + 4.; + + if (s->un.bar.repeat_bar) + s = s->prv; + } + + /* draw the repeat indications */ + repnl = 0; + for (s = first_repeat; s != 0; s = s->nxt) { + const char *p; + float w; + + if (s->type != BAR) + continue; + if (!s->un.bar.repeat_bar) + continue; + s1 = s; + i = 4; + s2 = 0; + for (;;) { + if (s->nxt == 0) + break; + s = s->nxt; + if (s->type == BAR) { + if (((s->un.bar.type & 0xf0) /* if complex bar */ + && s->un.bar.type != (B_OBRA << 4) + B_CBRA) + || s->un.bar.type == B_CBRA + || s->un.bar.repeat_bar) { + s2 = s; + break; + } + if (i == 4) + s2 = s; + if (--i < 0) /*fixme*/ + break; + } + } + if (s2 == 0) + s2 = s; +/*fixme*/ + if (s1 == s2) + break; + x = s1->x; + if ((s1->un.bar.type & 0x0f) == B_COL) + x -= 4; + i = 0; /* no end of bracket */ + w = s2->x - x - 8; + if (s2->type != BAR) + w = realwidth - x - 4; + else if (((s2->un.bar.type & 0xf0) /* if complex bar */ + && s2->un.bar.type != (B_OBRA << 4) + B_CBRA) + || s2->un.bar.type == B_CBRA) { + i = 2; + if ((s2->un.bar.type & 0x0f) == B_COL) + w -= 4; + else if (!(s2->sflags & S_RRBAR) + || s2->un.bar.type == B_CBRA + || s2->un.bar.type == (B_CBRA << 4) + B_BAR) + w += 8; /* explicit repeat end */ + if (p_voice != first_voice) + w -= 4; + } + p = s1->text; + if (p == 0) { + i--; /* no start of bracket */ + p = ""; + } + if (i == 0 && s2->nxt == 0) { /* 2nd ending at end of line */ + if (p_voice->bar_start != 0) + i = 2; + else repnl = 1; /* continue on next line */ + } + if (i >= 0) { + PUT2("(%s) %d ", p, i); + PUT4("%.1f %.1f \x01%c%5.2f repbra\n", + w, x, '0' + s1->staff, y); + set_y(s1, 1, x, w, y + 2.); + } + if (s->un.bar.repeat_bar) + s = s->prv; + } + if (repnl) { + p_voice->bar_start = B_OBRA; + p_voice->bar_repeat = 1; + } + } + + /* create the decorations tied to the staves */ + memset(minmax, 0, sizeof minmax); + for (de = deco_head; de != 0; de = de->next) { + deco_def_s *dd; + + dd = &deco_def_tb[de->t]; + if (dd->func < 6) /* if not tied to the staff */ + continue; + func_tb[dd->func](de); + if (de->flags & DE_UP) { + if (de->y > minmax[de->staff].ymax) + minmax[de->staff].ymax = de->y; + } else { + if (de->y < minmax[de->staff].ymin) + minmax[de->staff].ymin = de->y; + } + } + + /* and set them at a same vertical offset */ + for (de = deco_head; de != 0; de = de->next) { + deco_def_s *dd; + + dd = &deco_def_tb[de->t]; + if (dd->ps_func < 0 + || dd->func < 6) + continue; + x = de->x; + if (de->flags & DE_UP) + y = minmax[de->staff].ymax; + else y = minmax[de->staff].ymin; + de->y = y; + if (de->flags & DE_UP) + y += dd->h; + set_y(de->s, de->flags & DE_UP, x, de->v, y); + } + + /* draw the measure numbers */ + if (cfmt.measurenb >= 0) { + char *showm; + int bar_time, any_nb; + float wmeasure; + + showm = (char*)(cfmt.measurebox ? "showb" : "show"); + any_nb = 0; + + /* get the current bar number */ + for (s = first_voice->sym; + s->nxt != 0; /* ?? */ + s = s->nxt) { + switch (s->type) { + case TIMESIG: + wmeasure = s->un.meter.wmeasure; + case CLEF: + case KEYSIG: + case PART: + case TEMPO: + continue; + case BAR: + if (s->u != 0) + nbar = s->u; /* (%%setbarnb) */ + else if (s->un.bar.repeat_bar + && s->text != 0 + && s->text[0] != '1') + nbar = nbar_rep; /* restart bar numbering */ + break; + default: + break; + } + break; + } + if (nbar > 1) { + if (cfmt.measurenb == 0) { + set_font(&cfmt.measurefont); + any_nb = 1; + PUT4(" 0 \x01%c%5.2f M (%d) %s", + '0', 24. + 14., + nbar, showm); + } else if (nbar % cfmt.measurenb == 0) { + x = s->x - s->wl - 8.; + set_font(&cfmt.measurefont); + any_nb = 1; + y = get_y(s, 1, s->x, 20., 0); + if (y < s->dc_top + 5) + y = s->dc_top + 5; + if (s->nxt != 0 + && y < s->nxt->dc_top - 5) + y = s->nxt->dc_top - 5; + set_y(s, 1, s->x, 20., y + 6.); + PUT5("%.1f \x01%c%5.2f M (%d) %s", + x, '0', y, nbar, showm); + } + } + +/*fixme: KO when no bar at the end of the previous line */ + wmeasure = first_voice->meter.wmeasure; + bar_time = (int)(first_voice->sym->time + wmeasure); + for (s = first_voice->sym; s != 0; s = s->nxt) { + switch (s->type) { + case TIMESIG: + wmeasure = s->un.meter.wmeasure; + bar_time = (int)(s->time + wmeasure); + continue; + case MREST: + case MREP: + nbar += s->un.bar.len - 1; + default: + continue; + case BAR: + break; + } + if (s->u != 0) + nbar = s->u; /* (%%setbarnb) */ + if (s->time < bar_time /* incomplete measure */ + || s->un.bar.type == B_INVIS + || s->un.bar.type == B_CBRA) + continue; + if (s->u == 0) { + nbar++; + if (s->un.bar.repeat_bar + && s->text != 0) { + if (s->text[0] == '1') + nbar_rep = nbar; + else nbar = nbar_rep; /* restart bar numbering */ + } + } + bar_time = (int)(s->time + wmeasure); + if (s->nxt == 0 + || cfmt.measurenb == 0 + || (nbar % cfmt.measurenb) != 0 + || nbar <= 1) + continue; + if (!any_nb) { + any_nb = 1; + set_font(&cfmt.measurefont); + } +/*fixme: compute the real width of the number */ + y = get_y(s, 1, s->x, 20., 0); + if (y < s->dc_top + 5) + y = s->dc_top + 5; +/* fixme: have the number just right below the top of the next symbol*/ + if (s->nxt != 0 + && y < s->nxt->dc_top - 5) + y = s->nxt->dc_top - 5; + set_y(s, 1, s->x, 20., y + 6.); + PUT5(" %.1f \x01%c%5.2f M (%d) %s", + s->x, '0', y, + nbar, showm); + } + if (any_nb) + PUT0("\n"); + } +} + +/* -- draw guitar chords -- */ +/* (the staves are not yet defined) */ +static void draw_gchord(SYMBOL *s, + float gchy) +{ + float x, y, xspc, yspc, gchyb, gchyl, gchyr; + float xmin, xmax, ymin, ymax; + int box; + char *p, *q; + char t[81]; + + /* compute the y offset of all position types */ + yspc = cfmt.gchordfont.size * cfmt.gchordfont.swfac; + if (gchy < 34.) + gchy = 34.; + ymin = gchy; + gchy -= yspc; + gchyb = s->dc_bot - yspc; + gchyl = gchyr = s->y - yspc * 0.75; + p = s->text; + for (;;) { + if ((q = strchr(p, '\n')) != 0) + *q = '\0'; + switch (*p) { + default: /* default = above */ + case '^': /* above */ + gchy += yspc; + break; + case '_': /* below */ + break; + case '<': /* left */ + gchyl += yspc * 0.5; + break; + case '>': /* right */ + gchyr += yspc * 0.5; + break; + case '@': /* absolute */ + break; + } + if (q == 0) + break; + *q = '\n'; + p = q + 1; + } + ymax = s->dc_top = gchy + yspc; + xmin = xmax = s->type==MREST ? s->x-s->wl : s->x; + box = cfmt.gchordbox; + + /* loop on each line */ + p = s->text; + for (;;) { + if ((q = strchr(p, '\n')) != 0) + *q = '\0'; + x = s->type==MREST ? s->x-s->wl : s->x; + tex_str(t, p, sizeof t, &xspc); + p = t; + xspc *= cfmt.gchordfont.size; + switch (*p) { + case '^': /* above */ + case '_': /* below */ + if (*p == '^') + xspc -= cwid('^') * cfmt.gchordfont.size; + else xspc -= cwid('_') * cfmt.gchordfont.size; + p++; + /* fall thru */ + default: { /* default = above */ + float tmp; + + tmp = xspc; + xspc *= GCHPRE; + if (xspc > 8) + xspc = 8; + x -= xspc; + + if (t[0] == '_') { + y = gchyb; + s->dc_bot = gchyb - 2; + gchyb -= yspc; + } else { + y = gchy; + gchy -= yspc; + if (box && *p != '^') { + if (x < xmin) + xmin = x; + tmp += x; + if (tmp > xmax) + xmax = tmp; + box = 2; + } + } + break; + } + case '<': /* left */ +/*fixme: what symbol space?*/ + x -= xspc - cwid('<') * cfmt.gchordfont.size + 6; + y = gchyl; + gchyl -= yspc; + p++; + break; + case '>': /* right */ +/*fixme: what symbol space?*/ + x += 6; + y = gchyr; + gchyr -= yspc; + p++; + break; + case '@': { /* absolute */ + int n; + float xo, yo; + + p++; + if (sscanf(p, "%f,%f%n", &xo, &yo, &n) != 2) { + alert("Error in guitar chord \"@\" format (line %d)",s->linenum); + y = s->y + yo; + } else { + x += xo; + y = s->y + yo; + p += n; + } + break; + } + } + + PUT4("%.1f \x01%c%5.2f M (%s) gcshow ", + x, '0' + s->staff, y, p); + if (q == 0) + break; + *q = '\n'; + p = q + 1; + } + + /* draw the box */ + if (box == 2) { /* if any normal guitar chord */ + PUT5("%.1f \x01%c%5.2f %.1f %.1f box", + xmin - 2, '0' + s->staff, ymin - 3, + xmax - xmin + 6, ymax - ymin + 2); + } + + PUT0("\n"); +} + +/* -- get the beat from a time signature -- */ +static int get_beat(meter_s *m) +{ + int top, bot; + + if (m->meter[0].top[0] == 'C') { + if (m->meter[0].top[0] == '|') + return BASE_LEN / 2; + return BASE_LEN / 4; + } + if (m->meter[0].bot[0] == '\0') + return BASE_LEN / 4; + sscanf(m->meter[0].top, "%d", &top); + sscanf(m->meter[0].bot, "%d", &bot); + if (bot >= 8 && top >= 6 && top % 3 == 0) + return BASE_LEN * 3 / 8; + return BASE_LEN / bot; +} + +/* -- draw the parts and the tempo information -- */ +/* (the staves are being defined) */ +float draw_partempo(float top, + int any_part, + int any_tempo, + int any_vocal) +{ + SYMBOL *s; + float h, ht, w, y, ymin, nw, dy; + int i; + char tmp[128]; + + /* put the tempo indication at top */ + dy = 0; + if (any_tempo) { + int head, dots, flags, beat; + float sc, dx; + + ht = cfmt.tempofont.size + 2. + 2.; + if (any_vocal) + dy = ht; + else { + nw = 6. + cwid(' ') * cfmt.tempofont.size * 6; + /*fixme:have tempo on other voices but the 1st?*/ + + /* get the minimal y offset */ + ymin = 24. + 12.; + for (s = first_voice->sym; s != 0; s = s->nxt) { + if (s->type != TEMPO) + continue; + + if (s->un.tempo.str1 != 0) { + tex_str(tmp, s->un.tempo.str1, sizeof tmp, &w); + nw += w * cfmt.tempofont.size; + } + if (s->un.tempo.value != 0) { + i = 1; + while (i < sizeof s->un.tempo.length + / sizeof s->un.tempo.length[0] + && s->un.tempo.length[i] > 0) { + nw += 10; + i++; + } + nw += 10 + 10; + } + if (s->un.tempo.str2 != 0) { + tex_str(tmp, s->un.tempo.str2, sizeof tmp, &w); + nw += w * cfmt.tempofont.size; + } + y = get_y(s, 1, s->x - 5., nw + 30. + 10., 0) + 2.; + if (y > ymin) + ymin = y; + } + if (top < ymin + ht) + dy = ymin + ht - top; + } + + /* draw the tempo indications */ + set_font(&cfmt.tempofont); + sc = 0.7 * cfmt.tempofont.size / 15.0; /*fixme: 15.0 = initial tempofont*/ + beat = get_beat(&first_voice->meter); + for (s = first_voice->sym; s != 0; s = s->nxt) { + if (s->type != TEMPO) { + if (s->type == TIMESIG) + beat = get_beat(&s->un.meter); + continue; + } + + /*fixme: cf left shift (-5.)*/ + PUT2("%.1f %.1f M ", s->x - 5., 2. - ht); + if (s->un.tempo.str1 != 0) { + tex_str(tmp, s->un.tempo.str1, sizeof tmp, 0); + PUT1("(%s) show\n", tmp); + } + + /* draw the tempo indication, if specified */ + if (s->un.tempo.value != 0) { + int j; + + if (s->un.tempo.length[0] == 0) + s->un.tempo.length[0] = beat; + j = 0; + while (j < sizeof s->un.tempo.length + / sizeof s->un.tempo.length[0] + && s->un.tempo.length[j] > 0) { + identify_note(s, s->un.tempo.length[j], + &head, &dots, &flags); + PUT1("gsave %.2f dup scale 15 3 RM currentpoint\n", + sc); + switch (head) { + case H_OVAL: + PUT0("HD"); + break; + case H_EMPTY: + PUT0("Hd"); + break; + default: + PUT0("hd"); + break; + } + dx = 4.0; + if (dots) { + float dotx; + + dotx = 8; + if (flags > 0) + dotx += 4; + switch (head) { + case H_SQUARE: + case H_OVAL: + dotx += 2; + break; + case H_EMPTY: + dotx += 1; + break; + } + for (i = 0; i < dots; i++) { + PUT1(" %.1f 0 dt", dotx); + dx = dotx; + dotx += 3.5; + } + } + /* (16 is the stem height) */ + if (s->un.tempo.length[j] < SEMIBREVE) { + if (flags <= 0) + PUT1(" %d su", STEM); + else { + PUT2(" %d %d sfu", flags, STEM); + if (dx < 6.0) + dx = 6.0; + } + } + PUT1(" grestore %.2f 0 RM\n", + (dx + 18) * sc); + j++; + } + PUT1("( = %d) show\n", + s->un.tempo.value); + } + + if (s->un.tempo.str2 != 0) { + tex_str(tmp, s->un.tempo.str2, sizeof tmp, 0); + PUT1("(%s) show\n", tmp); + } + } + } else ht = 0; + + /* then, put the parts */ + if (!any_part) + return dy; + +/*fixme: should reduce if parts don't overlap tempo...*/ + h = cfmt.partsfont.size + 2. + 2.; /* + cfmt.partsspace; */ + + if (any_vocal) + dy += h; + else { + ymin = 24. + 14.; + for (s = first_voice->sym; s != 0; s = s->nxt) { + if (s->type != PART) + continue; + tex_str(tmp, &s->text[2], sizeof tmp, &w); + w *= cfmt.partsfont.size; + y = get_y(s, 1, s->x - 10., w + 15., 0) + 5.; + if (ymin < y) + ymin = y; + } + if (top < ymin + h + ht) + dy = ymin + h + ht - top; + } + + set_font(&cfmt.partsfont); + for (s = first_voice->sym; s != 0; s = s->nxt) { + if (s->type != PART) + continue; + tex_str(tmp, &s->text[2], sizeof tmp, 0); + PUT4("%.1f %.1f M (%s) show%s\n", + s->x - 10., 2. - ht - h, tmp, cfmt.partsbox ? "b" : ""); + } + return dy; +} + +/* -- initialize the default decorations -- */ +void reset_deco() +{ + memset(&deco_glob, 0, sizeof deco_glob); + + /* standard */ + deco_glob['.'] = 1; + deco_glob['H'] = 3; + deco_glob['L'] = 4; + deco_glob['M'] = 5; + deco_glob['O'] = 6; + deco_glob['P'] = 7; + deco_glob['S'] = 8; + deco_glob['T'] = 9; + deco_glob['u'] = 10; + deco_glob['v'] = 11; + + /* non-standard */ + deco_glob['~'] = 12; + deco_glob['J'] = 13; + deco_glob['R'] = 2; +} diff --git a/src-abcm2ps/default.fmt b/src-abcm2ps/default.fmt new file mode 100644 index 0000000..300ecc0 --- /dev/null +++ b/src-abcm2ps/default.fmt @@ -0,0 +1,35 @@ + +% Tight format to save space. +% Use automatic linebreaking and reduced space between notes. + +% parameters for typesetting music + scale 0.65 + continueall + maxshrink 0.90 + staffsep 43pt + +% set fonts + titlefont Helvetica-Bold 20 + subtitlefont Helvetica-Bold 10 + composerfont Times-Italic 10 + partsfont Times-Bold 10 + vocalfont Times-Bold 13 % note: gets scaled by "scale" + gchordfont Times-Roman 16 + textfont Times-Roman 10 + wordsfont Times-Roman 10 + +% spaces + topspace 0.50cm + titlespace 0.20cm + subtitlespace 0.10cm + composerspace 0.10cm + musicspace 0.20cm + partsspace 0.10cm + wordsspace 0.00cm + textspace 0.20cm + vocalspace 18pt + +% for typesetting text + lineskipfac 1.1 + parskipfac 0.3 + gchordbox 1 diff --git a/src-abcm2ps/draw.cpp b/src-abcm2ps/draw.cpp new file mode 100644 index 0000000..321040a --- /dev/null +++ b/src-abcm2ps/draw.cpp @@ -0,0 +1,2874 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +struct BEAM { /* packages info on one beam */ + struct SYMBOL *s1, *s2; + float a, b; + float x, y, t; + short stem, staff; +}; + +static void draw_note(float x, + struct SYMBOL *s, + int fl); + +/* -- check if space enough in the output buffer -- */ +static void nbuf_check(void) +{ + if (nbuf + 100 > BUFFSZ) { + alert( "PS output exceeds reserved space per staff -- increase BUFFSZ"); + return; + } +} + +/* -- up/down shift needed to get k*6 -- */ +static float rnd6(float y) +{ + int iy; + + iy = (int) (y + 2.999) / 6 * 6; + return iy - y; +} + +/* -- compute the best vertical offset for the beams -- */ +static float b_pos(int stem, + int flags, + float b) +{ + float d1, d2; + float top, bot; + + if (stem > 0) { + bot = b - (flags - 1) * BEAM_SHIFT - BEAM_DEPTH; + if (bot > 26) + return 0; + top = b; + } else { + top = b + (flags - 1) * BEAM_SHIFT + BEAM_DEPTH; + if (top < -2) + return 0; + bot = b; + } + + d1 = rnd6(top - BEAM_OFFSET); + d2 = rnd6(bot + BEAM_OFFSET); + if (d1 * d1 > d2 * d2) + return d2; + return d1; +} + +/* -- same as previous, but for grace note -- */ +static float b_gpos(int stem, + int flags, + float b) +{ + float d1, d2; + float top, bot; + + bot = b - (flags - 1) * 3 - 1.7; + if (bot > 26) + return 0; + top = b; + + d1 = rnd6(top - BEAM_OFFSET); + d2 = rnd6(bot + BEAM_OFFSET); + if (d1 * d1 > d2 * d2) + return d2; + return d1; +} + +/* -- calculate_beam -- */ +/* (the staves may be defined or not) */ +static int calculate_beam(struct BEAM *bm, + struct SYMBOL *s1) +{ + struct SYMBOL *s, *s2; + int notes, flags, staff, voice; + float x, y, ys, a, b, max_stem_err; + float sx, sy, sxx, sxy, syy, a0, stem_xoff; + int two_staves; + + /* find first and last note in beam */ + notes = flags = 0; /* set x positions, count notes and flags */ + two_staves = 0; + staff = s1->staff; + stem_xoff = s1->un.note.grace ? GSTEM_XOFF : STEM_XOFF; + for (s = s1; ; s = s->nxt) { + s->xs = s->x; + if (s->type != NOTE) + continue; + if (s->stem > 0) + s->xs += stem_xoff + s->shhd[0]; + else s->xs += -stem_xoff + s->shhd[s->nhd]; + if (s->nflags > flags) + flags = s->nflags; + notes++; + if (s->staff != staff) + two_staves = 1; + if (s->un.note.word_end) + break; + } + + if ((s2 = s) == 0) { + alert("No beam end! (line %d)",s1->linenum); + return 0; + } + + bm->s2 = s2; /* (don't display the flags) */ + if (bm->staff >= 0) { /* staves not defined */ + if (two_staves) { + for (s = s1; ; s = s->nxt) { + if (s->type == NOTE) + s->sflags |= S_2S_BEAM; + if (s == s2) + break; + } + return 0; + } + bm->staff = staff; + } else { /* staves defined */ + if (!two_staves && !s1->un.note.grace) + return 0; + } + + sx = sy = sxx = sxy = syy = 0; /* linear fit through stem ends */ + for (s = s1; ; s = s->nxt) { + if (s->type != NOTE) + continue; + x = s->xs; + y = s->ys + staff_tb[s->staff].y; + sx += x; sy += y; + sxx += x * x; sxy += x * y; syy += y * y; + if (s == s2) + break; + } + + /* beam fct: y=ax+b */ + a = (sxy * notes - sx * sy) / (sxx * notes - sx * sx); + b = (sy - a * sx) / notes; + + /* the next few lines modify the slope of the beam */ + if (!s1->un.note.grace) { + if (notes >= 3) { + float hh; + + hh = syy - a * sxy - b * sy; /* flatten if notes not in line */ + if (hh > 0 + && hh / (notes - 2) > 0.5) + a *= BEAM_FLATFAC; + } + if (a >= 0) + a = BEAM_SLOPE * a / (BEAM_SLOPE + a); /* max steepness for beam */ + else a = BEAM_SLOPE * a / (BEAM_SLOPE - a); + } else { + if (a > BEAM_SLOPE) + a = BEAM_SLOPE; + else if (a < -BEAM_SLOPE) + a = -BEAM_SLOPE; + } + + /* to decide if to draw flat etc. use normalized slope a0 */ + a0 = a * (s2->xs - s1->xs) / (20 * (notes - 1)); + + if (a0 * a0 < BEAM_THRESH * BEAM_THRESH) + a = 0; /* flat below threshhold */ + + b = (sy - a * sx) / notes; /* recalculate b for new slope */ + +/* if (flags>1) b=b+2*stem;*/ /* leave a bit more room if several beams */ + + /* have flat beams when asked */ + if (cfmt.flatbeams + && voice_tb[s1->voice].key.bagpipe) { + if (!s1->un.note.grace) + b = -11 + staff_tb[s1->staff].y; + else b = 35 + staff_tb[s1->staff].y; + a = 0; + } + +/*fixme: have a look again*/ + /* have room for the symbols in the staff */ + max_stem_err = 0; /* check stem lengths */ + voice = s1->voice; + s = s1; + if (two_staves) + /*fixme: to do*/ + ; + else if (!s1->un.note.grace) { + while (s->ts_prev->type == NOTE + && s->ts_prev->time == s->time) + s = s->ts_prev; + for (; s != 0 && s->time <= s2->time; s = s->ts_next) { + float min_stem, stem_err, slen; + + if (s->type != NOTE + || (s->staff != staff + && s->voice != voice)) { + continue; + } + if (s->voice == voice) { + ys = a * s->xs + b - staff_tb[s->staff].y; + if (s->nhd == 0) { + min_stem = STEM_MIN; + switch (s->nflags) { + case 2: min_stem = STEM_MIN2; break; + case 3: min_stem = STEM_MIN3; break; + case 4: min_stem = STEM_MIN4; break; + } + } else { + min_stem = STEM_CH_MIN; + switch (s->nflags) { + case 2: min_stem = STEM_CH_MIN2; break; + case 3: min_stem = STEM_CH_MIN3; break; + case 4: min_stem = STEM_CH_MIN4; break; + } + } + min_stem += BEAM_DEPTH + BEAM_SHIFT * (s->nflags - 1); + if (s->stem > 0) { + slen = ys - s->ymx; + if (s->pits[s->nhd] > 26) { + min_stem -= 2; + if (s->pits[s->nhd] > 28) + min_stem -= 2; + } + } else { + slen = s->ymn - ys; + if (s->pits[0] < 18) { + min_stem -= 2; + if (s->pits[0] < 16) + min_stem -= 2; + } + } + stem_err = min_stem - slen; + } else { +/*fixme: KO when two_staves */ + ys = a * s->x + b - staff_tb[s->staff].y; + if (s1->stem > 0) { + if (s->stem > 0) { +/*fixme: KO when the voice numbers are inverted */ + if (s->voice < voice) + continue; + if (s->y > ys) + continue; + stem_err = s->ys + 8. - ys; + } else stem_err = s->y + 8. - ys; + } else { + if (s->stem > 0) + stem_err = ys - s->y + 8.; + else { + stem_err = ys - s->ys + 8.; + if (s->y < ys) + continue; + } + } + } + if (stem_err > max_stem_err) + max_stem_err = stem_err; + } + } else { /* grace notes */ + for ( ; ; s = s->nxt) { + float min_stem, stem_err, slen; + + ys = a * s->xs + b - staff_tb[s->staff].y; + min_stem = GSTEM; + slen = ys - s->ymx; + stem_err = min_stem - slen; + if (stem_err > max_stem_err) + max_stem_err = stem_err; + if (s == s2) + break; + } + } + + if (max_stem_err > 0) /* shift beam if stems too short */ + b += s1->stem * max_stem_err; + + /* have room for the gracenotes */ + for (s = s1->nxt; ; s = s->nxt) { + struct SYMBOL *g; + + if ((g = s->grace) == 0) { + if (s == s2) + break; + continue; + } + x = s->x; + for (; g != 0; g = g->nxt) { + float yyg, _try; + + yyg = a * (x + g->x) + b; + + if (s->nxt->stem > 0) { + _try = g->ys - yyg + + BEAM_DEPTH + (flags - 1) * BEAM_SHIFT + + 2; + if (_try > 0) + b += _try; + } else { + _try = g->y - yyg + - BEAM_DEPTH - (flags - 1) * BEAM_SHIFT + - 7; + if (_try < 0) + b += _try; + } + } + if (s == s2) + break; + } + + if (a == 0) { /* shift flat beams onto staff lines */ + if (!s1->un.note.grace) + b += b_pos(s1->stem, flags, b - staff_tb[s1->staff].y); + else b += b_gpos(s1->stem, flags, b - staff_tb[s1->staff].y); + } + + /* adjust final stems and rests under beam */ + for (s = s1; ; s = s->nxt) { + switch (s->type) { + case NOTE: + s->ys = a * s->xs + b - staff_tb[s->staff].y; + if (s->stem > 0) { + if (s->dc_top < s->ys + 2) + s->dc_top = s->ys + 2; + } else { + if (s->dc_bot > s->ys - 2) + s->dc_bot = s->ys - 2; + } + break; + case REST: + y = a * s->xs + b - staff_tb[s->staff].y; + if (s1->stem > 0) { + y -= BEAM_DEPTH + (flags - 1) * BEAM_SHIFT; + y -= s->head != H_FULL ? 4 : 9; + if (s1->multi == 0 && y > 12) + y = 12; + if (s->y <= y) + break; + } else { + y += BEAM_DEPTH + (flags - 1) * BEAM_SHIFT; + y += s->head != H_FULL ? 4 : 11; + if (s1->multi == 0 && y < 12) + y = 12; + if (s->y >= y) + break; + } + if (s->head != H_FULL) { + int iy; + + iy = (int) (y + 3.0) / 6 * 6; + y = iy; + } + s->y = (int)y; + break; + } + if (s == s2) + break; + } + + /* save beam parameters */ + bm->s1 = s1; + bm->a = a; + bm->b = b; + bm->stem = s1->stem; /* general direction */ + if (!s1->un.note.grace) + bm->t = s1->stem * BEAM_DEPTH; + else bm->t = s1->stem * 1.6; + return 1; +} + +/* -- draw a single beam -- */ +/* (the staves may be defined or not) */ +static void draw_beam(float x1, + float x2, + float dy, + struct BEAM *bm) +{ + float y1, dy2; + + y1 = bm->a * x1 + bm->b - dy; + dy2 = bm->a * (x2 - x1); + PUT4("%.1f %.1f %.1f %.1f ", + bm->t, x2 - x1, dy2, x1); + if (bm->staff < 0) + PUT1("%.1f bm\n", y1); + else PUT2("\x01%c%5.2f bm\n", '0' + bm->staff, y1); +} + +/* -- draw the beams for one word -- */ +/* (the staves may be defined or not) */ +static void draw_beams(struct BEAM *bm) +{ + struct SYMBOL *s, *s1, *s2; + int i, maxfl; + float shift, bshift, bstub; + int two_staves; + + nbuf_check(); + s1 = bm->s1; + s2 = bm->s2; + if (!s1->un.note.grace) { + bshift = BEAM_SHIFT; + bstub = BEAM_STUB; + } else { + bshift = 3; + bstub = 3.2; + } + + /* make first beam over whole word */ + maxfl = 1; + for (s = s1; ; s = s->nxt) { + if (s->nflags > maxfl) + maxfl = s->nflags; + if (s == s2) + break; + } + draw_beam(s1->xs, s2->xs, 0.0, bm); + + /* other beams with two or more flags */ + two_staves = bm->staff < 0; + shift = 0; + for (i = 2; i <= maxfl; i++) { + struct SYMBOL *k1, *k2; + int inbeam; + + k1 = k2 = s1; + inbeam = 0; + shift += bshift; + for (s = s1; ; s = s->nxt) { + if (s->type != NOTE) { +#if 0 + if (s == s2) /* (not useful) */ + break; +#endif + continue; + } + if (!inbeam && s->nflags >= i) { + k1 = s; + inbeam = 1; + } + if (inbeam && (s->nflags < i + || s == s2 + || (s->sflags & S_BEAM_BREAK))) { + float x1; + + inbeam = 0; + if (s->nflags >= i) + k2 = s; + x1 = k1->xs; + if (k1 == k2) { + if (k1 == s1) + x1 += bstub; + else if (k1 == s2 || (k1->sflags & S_BEAM_BREAK)) { + x1 -= bstub; + } else { + struct SYMBOL *k; + + k = k1->prv; + while (k != 0 && k->type != NOTE) + k = k->prv; + if (k == 0 + || (k->sflags & S_BEAM_BREAK) + || k->nflags < k1->nxt->nflags + || (k->nflags == k1->nxt->nflags + && k->dots < k1->nxt->dots)) + x1 += bstub; + else x1 -= bstub; + } + } + draw_beam(x1, k2->xs, + shift * k1->stem, /*fixme: more complicated */ + bm); + + /* if on 2 staves, update the stem lengths */ + if (two_staves) { + two_staves = k1->stem; + for (;;) { + if (k1->type == NOTE) { + if (k1->stem != s1->stem) { + k1->ys = bm->a * k1->xs + bm->b + - staff_tb[k1->staff].y + - bm->t; + if (k1->stem != two_staves) + k1->ys -= two_staves * shift; + } + } + if (k1 == k2) + break; + k1 = k1->nxt; + } + } + } + if ((k2 = s) == s2) + break; + } + } +} + +/* -- draw the name/subname of the voices -- */ +static void draw_vname(int first_line, + float indent) +{ + struct VOICE_S *p_voice; + int n, staff; + struct { + int nl; + char *v[8]; + } staff_d[MAXSTAFF], *staff_p; + char *p, *q; + char t[64]; + float y; + + memset(staff_d, 0, sizeof staff_d); + n = 0; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + staff = p_voice->staff; + if (staff_tb[staff].brace_end) + staff--; + staff_p = &staff_d[staff]; + if (first_line) { + if ((p = p_voice->nm) == 0) + continue; + } else { + if ((p = p_voice->snm) == 0) + continue; + } + for (;;) { + staff_p->v[staff_p->nl++] = p; + if ((p = strstr(p, "\\n")) == 0) + break; + p += 2; + } + n++; + } + if (n == 0) + return; + set_font(&cfmt.vocalfont); + indent = -indent * 0.5; + for (staff = nstaff; staff >= 0; staff--) { + staff_p = &staff_d[staff]; + if (staff_p->nl == 0) + continue; + y = staff_tb[staff].y + 12. + 9. * (staff_p->nl - 1) + - cfmt.vocalfont.size * 0.3; + if (staff_tb[staff].brace) + y -= (staff_tb[staff].y - staff_tb[staff + 1].y) * 0.5; + for (n = 0; n < staff_p->nl; n++) { + p = staff_p->v[n]; + if ((q = strstr(p, "\\n")) != 0) + *q = '\0'; + tex_str(t, p, sizeof t, 0); + PUT3("%.1f %.1f M (%s) cshow\n", indent, y, t); + y -= 18.; + if (q != 0) + *q = '\\'; + } + } +} + +/* -- draw the left side of the staves -- */ +static void draw_lstaff(float x) +{ + int i; + + PUT3("%.1f %.1f %.1f bar\n", + staff_tb[0].y - staff_tb[nstaff].y + 24., + x, staff_tb[nstaff].y); + for (i = 0; i <= nstaff; i++) { + float y; + + if (staff_tb[i].brace) { + y = staff_tb[i].y + 24.; + PUT3("%.1f %.1f %.1f brace\n", + y - staff_tb[++i].y, x, y); + } else if (staff_tb[i].bracket) { + y = staff_tb[i++].y + 24.; + while (!staff_tb[i].bracket_end) + i++; + PUT3("%.1f %.1f %.1f bracket\n", + y - staff_tb[i].y, x, y); + } + } +} + +/* -- draw the staves and the left side -- */ +void draw_staff(int first_line, + float indent) +{ + int i; + + if (indent != 0) + draw_vname(first_line, indent); /* draw the voices name/subnames */ + + /* draw the staves */ + for (i = nstaff; i >= 0; i--) { + PUT2("%.1f 0 %.1f staff\n", + realwidth, staff_tb[i].y); + } + + if (nstaff != 0) + draw_lstaff(0); +} + +/* -- draw the time signature -- */ +static void draw_timesig(float x, + struct SYMBOL *s) +{ + int i, j; + + if (s->un.meter.wmeasure == 0) + return; + x = x - s->wl + 3.5; + for (i = 0; i < s->un.meter.nmeter; i++) { + const char *f; char meter[64]; + int l; + float dx; + + if (s->un.meter.meter[i].top[0] == ' ') { + x += 6.; /* half-space */ + continue; + } + l = strlen(s->un.meter.meter[i].top); + if (s->un.meter.meter[i].bot[0] != '\0') { + int l2; + + sprintf(meter, "(%.8s) (%.2s) ", + s->un.meter.meter[i].top, + s->un.meter.meter[i].bot); + f = "tsig"; + l2 = strlen(s->un.meter.meter[i].bot); + if (l2 > l) + l = l2; + } else { + if (s->un.meter.meter[i].top[0] == 'C') { + if (s->un.meter.meter[i].top[1] != '|') + f = "csig"; + else f = "ctsig"; + meter[0] = '\0'; + } else if (s->un.meter.meter[i].top[0] == '(' + || s->un.meter.meter[i].top[0] == ')') { + sprintf(meter, "(\\%s) ", + s->un.meter.meter[i].top); + f = "stsig"; + } else { + sprintf(meter, "(%.8s) ", + s->un.meter.meter[i].top); + f = "stsig"; + } + } + dx = 7 * l; + for (j = nstaff; j >= 0; j--) + PUT4("%s%.1f %.1f %s\n", + meter, x + dx * 0.5, + staff_tb[j].y, f); + x += dx; + } +} + +/* -- draw a key signature -- */ +static void draw_keysig(struct VOICE_S *p_voice, + float x, + struct SYMBOL *s) +{ + int old_sf = s->u; + int staff = p_voice->staff; + float staffb = staff_tb[staff].y; + int i, clef_ix; + int shift, clef_shift; + + static char sharp_tb[7] = {24, 15, 27, 18, 9, 21, 12}; + static char flat_tb[7] = {12, 21, 9, 18, 6, 15, 3}; + static signed char sharp_cl[7] = {0, -15, -9, -3, -18, -12, -6}; + static signed char flat_cl[7] = {0, 6, -9, -3, 3, -12, -6}; + + /* memorize the current keysig of the voice */ + memcpy(&p_voice->key, &s->un.key, sizeof p_voice->key); + + if (p_voice->second) + return; + + clef_ix = 0 - 2; /* treble */ + switch (staff_tb[staff].clef.type) { + case ALTO: + clef_ix = 3 - 3; + break; + case BASS: + clef_ix = 6 - 4; + break; + } + clef_ix += staff_tb[staff].clef.line; + if (clef_ix < 0) + clef_ix += 7; + else if (clef_ix >= 7) + clef_ix -= 7; + + /* normal accidentals */ + if (s->un.key.nacc == 0) { + + /* if flats to sharps, or sharps to flats, put neutrals */ + if (s->un.key.sf == 0 + || old_sf * s->un.key.sf < 0) { + + /* old sharps */ + clef_shift = sharp_cl[clef_ix]; + for (i = 0; i < old_sf; i++) { + if ((shift = sharp_tb[i] + clef_shift) < -3) + shift += 21; + PUT2("%.1f %.1f nt0 ", x, staffb + shift); + x += 5; + } + + /* old flats */ + clef_shift = flat_cl[clef_ix]; + for (i = 0; i > old_sf; i--) { + if ((shift = flat_tb[-i] + clef_shift) < -3) + shift += 21; + PUT2("%.1f %.1f nt0 ", x, staffb + shift); + x += 5; + } + if (s->un.key.sf != 0) + x += 3; /* extra space */ + + /* if less sharps or flats, put neutrals */ + /* sharps */ + } else if (s->un.key.sf > 0) { + if (s->un.key.sf < old_sf) { + clef_shift = sharp_cl[clef_ix]; + for (i = s->un.key.sf; i < old_sf; i++) { + if ((shift = sharp_tb[i] + clef_shift) < -3) + shift += 21; + PUT2("%.1f %.1f nt0 ", x, staffb + shift); + x += 5; + } + x += 3; /* extra space */ + } + /* flats */ + } else /*if (s->un.key.sf < 0)*/ { + if (s->un.key.sf > old_sf) { + clef_shift = flat_cl[clef_ix]; + for (i = s->un.key.sf; i > old_sf; i--) { + if ((shift = flat_tb[-i] + clef_shift) < -3) + shift += 21; + PUT2("%.1f %.1f nt0 ", x, staffb + shift); + x += 5; + } + x += 3; /* extra space */ + } + } + + /* new sharps */ + clef_shift = sharp_cl[clef_ix]; + + for (i = 0; i < s->un.key.sf; i++) { + if ((shift = sharp_tb[i] + clef_shift) < -3) + shift += 21; + PUT2("%.1f %.1f sh0 ", x, staffb + shift); + x += 5; + } + + /* new flats */ + clef_shift = flat_cl[clef_ix]; + for (i = 0; i > s->un.key.sf; i--) { + if ((shift = flat_tb[-i] + clef_shift) < -3) + shift += 21; + PUT2("%.1f %.1f ft0 ", x, staffb + shift); + x += 5; + } + } else { + int last_acc; + + /* explicit accidentals */ + last_acc = s->un.key.accs[0]; + for (i = 0; i < s->un.key.nacc; i++) { + const char *p; + + shift = sharp_cl[clef_ix]; + p = "sh0"; + if (s->un.key.accs[i] != last_acc) { + last_acc = s->un.key.accs[i]; + x += 3; + } + switch (last_acc) { + case A_SH: + case A_DS: + break; + case A_FT: + case A_DF: + shift = flat_cl[clef_ix]; + p = "ft0"; + break; + case A_NT: + p = "nt0"; + break; + } + shift += 3 * (s->un.key.pits[i] - 18); + if (shift < -3) + shift += 21; + else if (shift >= 24 + 3) + shift -= 21; + PUT3("%.1f %.1f %s ", x, staffb + shift, p); + x += 5; + } + } + if (old_sf != 0 || s->un.key.sf != 0 || s->un.key.nacc != 0) + PUT0("\n"); +} + +/* -- draw a measure bar -- */ +static void draw_bar(float x, + struct SYMBOL *s) +{ + int staff; + float stafft, y; + int bar_type, dash; + const char *psf; + + dash = 0; + bar_type = s->un.bar.type; + switch (bar_type) { + case B_OBRA: + case B_CBRA: + case (B_OBRA << 4) + B_CBRA: + return; + case B_COL: + dash = 1; + bar_type = B_BAR; + break; + case (B_CBRA << 4) + B_BAR: + bar_type = B_BAR; + break; + case (B_BAR << 4) + B_COL: + bar_type |= (B_OBRA << 8); + break; + case (B_BAR << 8) + (B_COL << 4) + B_COL: + bar_type |= (B_OBRA << 12); + break; + case (B_BAR << 12) + (B_COL << 8) + (B_COL << 4) + B_COL: + bar_type |= (B_OBRA << 16); + break; + case (B_COL << 4) + B_BAR: + case (B_COL << 8) + (B_COL << 4) + B_BAR: + case (B_COL << 12) + (B_COL << 8) + (B_COL << 4) + B_BAR: + bar_type <<= 4; + bar_type |= B_CBRA; + break; + case (B_COL << 4) + B_COL: + bar_type = (B_COL << 12) + (B_CBRA << 8) + (B_OBRA << 4) + B_COL; + break; + } + for (;;) { + stafft = staff_tb[0].y + 24.; /* top of upper staff */ + psf = "bar"; + switch (bar_type & 0x0f) { + case B_BAR: + if (dash) + psf = "dabar"; + break; + case B_OBRA: + case B_CBRA: + psf = "thbar"; + x -= 3; + break; + case B_COL: + x -= 2; + break; + } + switch (bar_type & 0x0f) { + default: + for (staff = 0; ; staff++) { + if (staff_tb[staff].stop_bar + || staff == nstaff) { + y = staff_tb[staff].y; + PUT4("%.1f %.1f %.1f %s ", + stafft - y, x, y, psf); + if (staff == nstaff) + break; + stafft = staff_tb[staff + 1].y + 24.; + } + } + break; + case B_COL: + for (staff = nstaff; staff >= 0; staff--) + PUT2("%.1f %.1f rdots ", + x + 1, staff_tb[staff].y); + break; + } + bar_type >>= 4; + if (bar_type == 0) + break; + x -= 3; + } + PUT0("\n"); +} + +/* -- draw a rest -- */ +/* (the staves are defined) */ +static void draw_rest(struct SYMBOL *s) +{ + int i, y; + float x, dotx, staffb; + +static const char *rest_tb[9] = { + "r64", "r32", "r16", "r8", + "r4", + "r2", "r1", "r0", "r00" +}; + + if (s->un.note.invis) + return; + + x = s->x + s->shhd[0]; + y = s->y; + i = 4 - s->nflags; /* rest_tb index */ + + /* if rest alone in the measure, do it semibreve and center */ + if (s->un.note.len == voice_tb[s->voice].meter.wmeasure) { + struct SYMBOL *prev; +/*fixme: vertical spacing pb may occur when multi-voice*/ + i = 6; /* r1 */ + s->dots = 0; + if (s->nxt != 0) + x = s->nxt->x; + else x = realwidth; + prev = s->prv; + while (prev->type == TEMPO || prev->type == PART) + prev = prev->prv; + x = (x + prev->x) * 0.5; + + /* center the associated decorations */ + if (s->un.note.dc.n > 0) + deco_update(s, x - s->x); + } + + staffb = staff_tb[s->staff].y; /* bottom of staff */ + + PUT3("%.1f %.1f %s", x, y + staffb, rest_tb[i]); + + /* add helper line(s) */ + switch (i) { + case 8: /* breve / longa */ + case 7: + if (y >= 24) + PUT1(" %.1f hl", y + 6 + staffb); + if (i == 7) { + if (y <= -6) + PUT1(" %.1f hl", y + staffb); + } else { + if (y <= 0) + PUT1(" %.1f hl", y - 6 + staffb); + } + break; + case 6: /* semibreve */ + if (y < -6 + || y >= 24) + PUT1(" %.1f hl", y + 6 + staffb); + break; + case 5: /* minim */ + if (y <= -6 + || y >= 30) + PUT1(" %.1f hl", y + staffb); + break; + } + + dotx = 8.0; + for (i = 0; i < s->dots; i++) { + PUT1(" %.1f 3 dt", dotx); + dotx += 3.5; + } + PUT0("\n"); +} + +/* -- draw grace notes -- */ +/* (the staves are defined) */ +static void draw_gracenotes(float x, + struct SYMBOL *s) +{ + int yy; + float x0, y0, x1, y1, x2, y2, x3, y3, bet1, bet2, dy1, dy2; + float a, b, staffb; + struct SYMBOL *g, *last; + struct BEAM bm; + + a = b = 0; /* compiler warning */ + + staffb = staff_tb[s->staff].y; /* bottom of staff */ + + /* draw the notes */ + bm.s2 = 0; + bm.staff = -1; /* staves defined */ + for (g = s->grace; ; g = g->nxt) { + if (s->grace->nxt != 0) { /* many notes */ + if ((g->sflags & S_WORD_ST) + && !g->un.note.word_end) { + if (calculate_beam(&bm, g)) + draw_beams(&bm); + } + } + draw_note(g->x, g, bm.s2 == 0); + if (s == bm.s2) + bm.s2 = 0; + + if (g->un.note.sappo) + PUT0(" ga\n"); + + if (g->nxt == 0) { + last = g; + break; + } + } + + + /* slur */ + if (voice_tb[s->voice].key.bagpipe /* no slur when bagpipe */ + || !cfmt.graceslurs + || s->un.note.slur_st /* explicit slur */ + || s->nxt == 0 + || s->nxt->type != NOTE) + return; + yy = 1000; + for (g = s->grace; g != 0; g = g->nxt) { + if (g->y < yy) { + yy = g->y; + last = g; + } + } + x0 = last->x; + y0 = last->y - 5; + if (s->grace != last) { + x0 -= 4; + y0 += 1; + } + s = s->nxt; + x = s->x; + x3 = x - 1; + if (s->stem < 0) + x3 -= 3; + y3 = s->ymn - 5; + dy1 = (x3 - x0) * 0.4; + if (dy1 > 3) + dy1 = 3; + dy2 = dy1; + + bet1 = 0.2; + bet2 = 0.8; + if (last->y > s->ymn + 7) { + x0 = last->x - 1; + y0 = last->y - 4.5; + y3 = s->ymn + 1.5; + x3 = x - 5.5; + dy2 = (y0 - y3) * 0.2; + dy1 = (y0 - y3) * 0.8; + bet1 = 0; + } + + if (y3 > y0 + 4) { + y3 = y0 + 4; + x0 = last->x + 2; + y0 = last->y - 4; + } + + x1 = bet1 * x3 + (1 - bet1) * x0; + y1 = bet1 * y3 + (1 - bet1) * y0 - dy1; + x2 = bet2 * x3 + (1 - bet2) * x0; + y2 = bet2 * y3 + (1 - bet2) * y0 - dy2; + + PUT4(" %.1f %.1f %.1f %.1f", + x1, y1 + staffb, x2, y2 + staffb); + PUT4(" %.1f %.1f %.1f %.1f gsl\n", + x3, y3 + staffb, x0, y0 + staffb); +} + +/* -- draw m-th head with accidentals and dots -- */ +/* (the staves are defined) */ +static void draw_basic_note(float x, + struct SYMBOL *s, + int m) +{ + int y; + float staffb; + const char *p; + int head, dots, nflags; +static const char *acc_tb[] = { "", "sh", "nt", "ft", "dsh", "dft" }; + + staffb = staff_tb[s->staff].y; /* bottom of staff */ + y = 3 * (s->pits[m] - 18); /* height on staff */ + + /* special case when no head */ + if (s->sflags & S_NO_HEAD) { + if ((m == 0 && s->stem > 0) + || (m == s->nhd && s->stem < 0)) { + PUT2("/x %.1f def /y %.1f def", /* set x y */ + x + s->shhd[m], + y + staffb); + return; + } + } + + identify_note(s, s->un.note.lens[m], + &head, &dots, &nflags); + + /* draw the head */ + PUT2("%.1f %.1f ", x + s->shhd[m], y + staffb); + if (s->un.note.grace) + p = "ghd"; + else if (s->un.note.accs[m] + && voice_tb[s->voice].clef.type == PERC) + p = "2 copy /y exch def /x exch def dsh0"; + else { + switch (head) { + case H_SQUARE: + if (s->un.note.lens[m] < BREVE * 2) + p = "breve"; + else p = "longa"; + break; + case H_OVAL: + if (s->un.note.lens[m] < BREVE) + p = "HD"; + else p = "HDD"; + break; + case H_EMPTY: + p = "Hd"; break; + default: + p = "hd"; break; + } + } + PUT0(p); + + /* add a helper line if horizontal shift */ + if (s->shhd[m]) { + int yy; + + yy = 0; + if (y >= 30) { + yy = y; + if (yy % 6) + yy -= 3; + } else if (y <= -6) { + yy = y; + if (yy % 6) + yy += 3; + } + if (yy) + PUT1(" %.1f hl", yy + staffb); + } + + /* draw the dots */ +/*fixme: to see for grace notes*/ + if (dots) { + int i; + float dotx; + int doty; + + dotx = 8. + s->xmx - s->shhd[m]; + if (y % 6) + doty = 0; + else { + if ((doty = s->doty) == 0) /* defined when voices overlap */ + doty = 3; + } + if (s->nflags > 0 && s->stem > 0 + && (s->sflags & S_WORD_ST) + && s->un.note.word_end + && doty > 0) + dotx += DOTSHIFT; + switch (head) { + case H_SQUARE: + case H_OVAL: + dotx += 2; + break; + case H_EMPTY: + dotx += 1; + break; + } + for (i = 0; i < dots; i++) { + PUT2(" %.1f %d dt", dotx, doty); + dotx += 3.5; + } + } + + /* draw the accidental */ + if (s->un.note.accs[m] + && voice_tb[s->voice].clef.type != PERC) { + if (s->un.note.grace) + p = "g"; + else p = ""; + PUT3(" %.1f %s%s", + - s->shac[m], + p, acc_tb[s->un.note.accs[m]]); + } +} + +/* -- draw a note or a chord -- */ +/* (the staves are defined) */ +static void draw_note(float x, + struct SYMBOL *s, + int fl) +{ + int y, i, m, ma; + float staffb; + const char *hltype; + + staffb = staff_tb[s->staff].y; + + /* draw the master note - can be only the first or the last note */ + if (s->stem >= 0) + ma = 0; + else ma = s->nhd; + draw_basic_note(x, s, ma); /* draw the note head */ + if (!s->un.note.stemless) { /* add stem and flags */ + char c, c2; + float slen; + + c = s->stem > 0 ? 'u' : 'd'; + slen = s->stem * (s->ys - s->y); + if (!fl || s->nflags <= 0) { /* stem only */ + c2 = s->un.note.grace ? 'g' : 's'; + PUT3(" %.1f %c%c", slen, c2, c); + } else { /* stem and flags */ + if (voice_tb[(int) s->voice].key.bagpipe + && cfmt.straightflags) + c = 's'; /* straight flag */ + c2 = s->un.note.grace ? 'g' : 'f'; + PUT4(" %d %.1f s%c%c", s->nflags, slen, c2, c); + } + } + + if (s->un.note.grace) + hltype = "ghl"; + else { + switch (s->head) { + default: + hltype = "hl"; + break; + case H_SQUARE: + case H_OVAL: + hltype = "hl1"; + break; + } + } + y = s->ymn; /* lower helper lines */ + if (y <= -6) { + for (i = -6; i >= y; i -= 6) + PUT2(" %.1f %s", i + staffb, hltype); + } + y = s->ymx; /* upper helper lines */ + if (y >= 30) { + for (i = 30; i <= y; i += 6) + PUT2(" %.1f %s", i + staffb, hltype); + } + + /* draw the other notes */ + for (m = 0; m <= s->nhd; m++) { + if (m == ma) + continue; + PUT0(" "); + draw_basic_note(x, s, m); /* draw the note heads */ + } + + PUT0("\n"); +} + +/* -- draw_bracket -- */ +/* (the staves are not yet defined) */ +static void draw_bracket(struct SYMBOL *s1, + struct SYMBOL *s2) +{ + struct SYMBOL *sy; + int upstaff, nb_only; +/* int two_staves; */ + float x1, x2, y1, y2, xm, ym, s, s0, yy, yx, dy; + + /* search what is the upper staff and if a bracket is needed */ + nb_only = s1->type == NOTE && s2->type == NOTE; + if (!((s1->sflags & S_WORD_ST) || (s1->prv->sflags & S_BEAM_BREAK) + || s1->prv->nflags < s1->nflags) +/* || s1->prv->un.note.word_end) */ + || !(s2->un.note.word_end || (s2->sflags & S_BEAM_BREAK))) + nb_only = 0; + upstaff = s1->staff; +/* two_staves = 0; */ + for (sy = s1; ; sy = sy->nxt) { + if (sy->len == 0) { /* not a note or a rest */ + if (sy->type != GRACE) + nb_only = 0; + if (sy == s2) + break; + continue; + } + if (sy->staff != upstaff) { +/* two_staves = 1; */ + if (sy->staff < upstaff) + upstaff = sy->staff; +/* break; */ + } + if (sy == s2) + break; + if (sy->un.note.word_end || (sy->sflags & S_BEAM_BREAK)) + nb_only = 0; + } + + /* if nplet number only, draw it */ + if (nb_only) { + float a, b; + + a = (s2->ys - s1->ys) / (s2->xs - s1->xs); + b = s1->ys - a * s1->xs; + if (s1->stem > 0) { + if (s1->multi) { + for (sy = s1; ; sy = sy->nxt) { + yy = a * sy->xs + b; + if (sy->dc_top > yy) + b += sy->dc_top - yy; + if (sy == s2) + break; + } + } + b += 4.; + } else { + if (s1->multi) { + for (sy = s1; ; sy = sy->nxt) { + yy = a * sy->xs + b; + if (sy->dc_bot < yy) + b += sy->dc_bot - yy; + if (sy == s2) + break; + } + } + b -= 12.; + } + xm = (s2->xs + s1->xs) * 0.5; + ym = a * xm + b; + PUT4("(%d) %.1f \x01%c%5.2f bnum\n", + s1->un.note.p_plet, xm, '0' + s1->staff, ym); + if (s1->stem > 0) { + b += 8.; + for (sy = s1; ; sy = sy->nxt) { + yy = a * sy->xs + b; + if (sy->dc_top < yy) + sy->dc_top = yy; + if (sy == s2) + break; + } + } else { + for (sy = s1; ; sy = sy->nxt) { + yy = a * sy->xs + b; + if (sy->dc_bot > yy) + sy->dc_bot = yy; + if (sy == s2) + break; + } + } + return; + } + +/*fixme: two staves not treated*/ +/*fixme: to optimize*/ + if (s1->multi >= 0) { + + /* sole or upper voice: the bracket is above the staff */ + x1 = s1->x - 4.; + x2 = s2->x + 4.; + if (s1->staff == upstaff) { + sy = s1; + if (sy->type != NOTE) { + for (sy = sy->nxt; sy != s2; sy = sy->nxt) + if (sy->type == NOTE) + break; + } + y1 = sy->dc_top; + if (y1 < 24.) + y1 = 24.; + if (s1->stem > 0) + x1 += 3.; + } else y1 = 24.; + if (s2->staff == upstaff) { + sy = s2; + if (sy->type != NOTE) { + for (sy = sy->prv; sy != s1; sy = sy->prv) + if (sy->type == NOTE) + break; + } + y2 = sy->dc_top; + if (y2 < 24.) + y2 = 24.; + if (s2->stem > 0) + x2 += 3.; + } else y2 = 24.; + + xm = 0.5 * (x1 + x2); + ym = 0.5 * (y1 + y2); + + s = (y2 - y1) / (x2 - x1); + s0 = (s2->ymx - s1->ymx) / (x2 - x1); + if (s0 > 0) { + if (s < 0) + s = 0; + else if (s > s0) + s = s0; + } else { + if (s > 0) + s = 0; + else if (s < s0) + s = s0; + } + if (s * s < 0.1 * 0.1) + s = 0; + + /* shift up bracket if needed */ + dy = 0; + for (sy = s1; ; sy = sy->nxt) { + if (sy->len == 0 /* not a note nor a rest */ + || sy->staff != upstaff) { + if (sy == s2) + break; + continue; + } + yy = ym + (sy->x - xm) * s; + yx = sy->dc_top; + if (yx - yy > dy) + dy = yx - yy; + if (sy == s2) + break; + } + + ym += dy + 4.; + y1 = ym + s * (x1 - xm); + y2 = ym + s * (x2 - xm); + PUT3("%.1f \x01%c%5.2f ", x1, '0' + upstaff, y1 + 4.); + PUT3("%.1f \x01%c%5.2f hbr ", + xm - 6., '0' + upstaff, ym + s * -6. + 4.); + PUT3("%.1f \x01%c%5.2f ", x2, '0' + upstaff, y2 + 4.); + PUT3("%.1f \x01%c%5.2f hbr ", + xm + 6., '0' + upstaff, ym + s * 6. + 4.); + + /* shift the slurs / decorations */ + ym += 8.; + for (sy = s1; ; sy = sy->nxt) { + if (sy->staff == upstaff) { + yy = ym + (sy->x - xm) * s; + if (sy->dc_top < yy) + sy->dc_top = yy; + } + if (sy == s2) + break; + } + + } else { /* lower voice of the staff: the bracket is below the staff */ +/*fixme: think to all that again..*/ + x1 = s1->x - 8.; +/*fixme: the note may be shifted to the right*/ + x2 = s2->x; + if (s1->staff == upstaff) { + sy = s1; + if (sy->type != NOTE) { + for (sy = sy->nxt; sy != s2; sy = sy->nxt) + if (sy->type == NOTE) + break; + } + y1 = sy->dc_bot; + } else y1 = 0; + if (s2->staff == upstaff) { + sy = s2; + if (sy->type != NOTE) { + for (sy = sy->prv; sy != s1; sy = sy->prv) + if (sy->type == NOTE) + break; + } + y2 = sy->dc_bot; + } else y2 = 0; + + xm = 0.5 * (x1 + x2); + ym = 0.5 * (y1 + y2); + + s = (y2 - y1) / (x2 - x1); + s0 = (s2->ymn - s1->ymn) / (x2 - x1); + if (s0 > 0) { + if (s < 0) + s = 0; + else if (s > s0) + s = s0; + } else { + if (s > 0) + s = 0; + else if (s < s0) + s = s0; + } + if (s * s < 0.1 * 0.1) + s = 0; + + /* shift down bracket if needed */ + dy = 0; + for (sy = s1; ; sy = sy->nxt) { + if (sy->len == 0 /* not a note nor a rest */ + || sy->staff != upstaff) { + if (sy == s2) + break; + continue; + } + yy = ym + (sy->x - xm) * s; + yx = sy->dc_bot; + if (yx - yy < dy) + dy = yx - yy; + if (sy == s2) + break; + } + + ym += dy - 12.; + y1 = ym + s * (x1 - xm); + y2 = ym + s * (x2 - xm); + PUT3("%.1f \x01%c%5.2f ", x1, '0' + upstaff, y1 + 4.); + PUT3("dlw %.1f \x01%c%5.2f M lineto 0 3 RL stroke ", + xm - 6., '0' + upstaff, ym + s * -6. + 4.); + PUT3("%.1f \x01%c%5.2f ", x2, '0' + upstaff, y2 + 4.); + PUT3("%.1f \x01%c%5.2f M lineto 0 3 RL stroke ", + xm + 6., '0' + upstaff, ym + s * 6. + 4.); + + /* shift the slurs / decorations */ + for (sy = s1; ; sy = sy->nxt) { + if (sy->staff == upstaff) { + yy = ym + (sy->x - xm) * s; + if (sy->dc_bot > yy) + sy->dc_bot = yy; + } + if (sy == s2) + break; + } + } /* lower voice */ + + yy = 0.5 * (y1 + y2); + PUT4("(%d) %.1f \x01%c%5.2f bnum\n", + s1->un.note.p_plet, xm, '0' + upstaff, yy); +} + +/* -- draw_nplet_brackets -- */ +/* (the staves are not yet defined) */ +static void draw_nplet_brackets(struct SYMBOL *sym) +{ + struct SYMBOL *s, *s1; + + for (s = sym; s != 0; s = s->nxt) { + if ((s->sflags & (S_NPLET_ST|S_NPLET_END)) != S_NPLET_ST) + continue; + s1 = s; + for (; s->nxt != 0; s = s->nxt) { + if ((s->sflags & (S_NPLET_ST|S_NPLET_END)) == S_NPLET_END) + break; + } + draw_bracket(s1, s); + } +} + +/* -- output slur -- */ +static void output_slur(float x1, + float y1, + float x2, + float y2, + int s, + float height, + int staff) /* if < 0, the staves are defined */ +{ + float alfa, beta, mx, my, xx1, yy1, xx2, yy2, dx, dy, dz; + + alfa = 0.3; + beta = 0.45; + + /* for wide flat slurs, make shape more square */ + dy = y2 - y1; + if (dy < 0) + dy = -dy; + dx = x2 - x1; + if (dx > 40. && dy / dx < 0.7) { + alfa = 0.3 + 0.002 * (dx - 40.); + if (alfa > 0.7) + alfa = 0.7; + } + + /* alfa, beta, and height determine Bezier control points pp1,pp2 + * + * X====alfa===|===alfa=====X + * / | \ + * pp1 | pp2 + * / height \ + * beta | beta + * / | \ + * p1 m p2 + * + */ + + mx = 0.5 * (x1 + x2); + my = 0.5 * (y1 + y2); + + xx1 = mx + alfa * (x1 - mx); + yy1 = my + alfa * (y1 - my) + height; + xx1 = x1 + beta * (xx1 - x1); + yy1 = y1 + beta * (yy1 - y1); + + xx2 = mx + alfa * (x2 - mx); + yy2 = my + alfa * (y2 - my) + height; + xx2 = x2 + beta * (xx2 - x2); + yy2 = y2 + beta * (yy2 - y2); + + dx = 0.03 * (x2 - x1); + if (dx > 10.) + dx = 10.; + dy = 1.; + dz = 0.2; + if (x2 - x1 > 100.) + dz += 0.001 * (x2 - x1); + if (dz > 0.6) + dz = 0.6; + + if (staff < 0) { + PUT4("%.1f %.1f %.1f %.1f ", + xx2 - dx, yy2 + dy * s, xx1 + dx, yy1 + dy * s); + PUT3("%.1f %.1f 0 %.1f ", x1, y1 + dz * s, dz * s); + PUT4("%.1f %.1f %.1f %.1f ", xx1, yy1, xx2, yy2); + PUT4("%.1f %.1f %.1f %.1f SL\n", x2, y2, x1, y1); + } else { + PUT3("%.1f \x01%c%5.2f ", + xx2 - dx, '0' + staff, yy2 + dy * s); + PUT3("%.1f \x01%c%5.2f ", + xx1 + dx, '0' + staff, yy1 + dy * s); + PUT4("%.1f \x01%c%5.2f 0 %.1f ", + x1, '0' + staff, y1 + dz * s, dz * s); + PUT3("%.1f \x01%c%5.2f ", + xx1, '0' + staff, yy1); + PUT3("%.1f \x01%c%5.2f ", + xx2, '0' + staff, yy2); + PUT3("%.1f \x01%c%5.2f ", + x2, '0' + staff, y2); + PUT3("%.1f \x01%c%5.2f SL\n", + x1, '0' + staff, y1); + } +} + +/* -- decide whether a slur goes up or down -- */ +static int slur_direction(struct SYMBOL *k1, + struct SYMBOL *k2) +{ + struct SYMBOL *s; + int are_stems, are_downstems, y_max; + + are_stems = are_downstems = 0; + y_max = 300; + for (s = k1; ; s = s->nxt) { + if (s->type == NOTE) { + if (!s->un.note.stemless) { + are_stems = 1; + if (s->stem < 0) + are_downstems = 1; + } + if (y_max > s->ymn) + y_max = s->ymn; + } + if (s == k2) + break; + } + if (are_downstems + || (!are_stems + && y_max >= 12)) + return 1; + return -1; +} + +/* -- check if slur sequence in a multi-voice staff -- */ +static int slur_multi(struct SYMBOL *k1, + struct SYMBOL *k2) +{ + for (;;) { + if (k1->multi != 0) /* if multi voice */ + /*fixme: may change*/ + return k1->multi; + if (k1 == k2) + break; + k1 = k1->nxt; + } + return 0; +} + +/* -- draw a phrasing slur between two symbols -- */ +/* (the staves are not yet defined) */ +/* (not a pretty routine, this) */ +static void draw_slur(struct SYMBOL *k1, + struct SYMBOL *k2) +{ + struct SYMBOL *k; + float x1, y1, x2, y2, height, addx, addy; + float a, y, z, h, dx, dy; + int s, nn; + int upstaff; + int two_staves; + +/*fixme: if two staves, may have upper or lower slur*/ + nbuf_check(); + if ((s = slur_multi(k1, k2)) == 0) + s = slur_direction(k1, k2); + + nn = 1; + upstaff = k1->staff; + two_staves = 0; + for (k = k1->nxt; k != 0; k = k->nxt) { + if (k->len > 0) { /* note or rest */ + nn++; + if (k->staff != upstaff) { + two_staves = 1; + if (k->staff < upstaff) + upstaff = k->staff; + } + } + if (k == k2) + break; + } +/*fixme: KO when two staves*/ +if (two_staves) alert( "*** multi-staves slurs not treated"); + + /* fix endpoints */ + x1 = k1->x + k1->xmx; /* take the max right side */ + if (k1 != k2) + x2 = k2->x; + else x2 = realwidth; /* (the slur starts on last note of the line) */ + y1 = k1->y; + y2 = k2->y; + + if (k1->type == NOTE) { + if (k1->stem * s > 0) { + x1 += s * 4; + y1 = k1->ys + s * 2; + } else y1 += s * 6; + + if (s > 0) { + if (y1 < k1->dc_top + 2.5) + y1 = k1->dc_top + 2.5; + } else { + if (y1 > k1->dc_bot - 2.5) + y1 = k1->dc_bot - 2.5; + } + } + + if (k2->type == NOTE) { + if (k2->stem * s > 0) { + x2 += s * 3; + y2 = k2->ys + s * 2; + } else y2 += s * 6; + + if (s > 0) { + if (y2 < k2->dc_top + 2.5) + y2 = k2->dc_top + 2.5; + } else { + if (y2 > k2->dc_bot - 2.5) + y2 = k2->dc_bot - 2.5; + } + + /* special case when different stem directions */ + /* (assert s > 0) */ + if (k1->type == NOTE + && k1->stem != k2->stem) { + if (k1->stem > 0) { + if (y1 > y2 - 3) { + if ((k1->sflags & S_WORD_ST) + && k1->un.note.word_end) { + y2 += 6; + y1 = y2 - 3; + if (y1 < k1->dc_top + 2.5) + x1 += 3; + } else y2 = y1 + 3; + } + } else { + if (y2 > y1 - 3) { + if ((k2->sflags & S_WORD_ST) + && k2->un.note.word_end) { + y1 += 6; + y2 = y1 - 3; + if (y2 < k2->dc_top + 2.5) + x2 -= 3; + } else y1 = y2 + 3; + } + } + } + } + + if (k1->type != NOTE) { + y1 = y2 + 1.2 * s; + x1 = k1->x + k1->wr * 0.3; + if (x1 > x2 - 12) + x1 = x2 - 12; + } + + if (k2->type != NOTE) { + y2 = y1 + 1.2 * s; + if (k1 != k2) + x2 = k2->x - k2->wl * 0.3; + } + + if (nn > 3) { + if (s > 0) { + if (y1 < k1->nxt->dc_top) + y1 = k1->nxt->dc_top; + if (y2 < k2->prv->dc_top) + y2 = k2->prv->dc_top; + } else { + if (y1 > k1->nxt->dc_bot) + y1 = k1->nxt->dc_bot; + if (y2 > k2->prv->dc_bot) + y2 = k2->prv->dc_bot; + } + } + + /* shift endpoints */ + addx = 0.04 * (x2 - x1); + if (addx > 3.0) + addx = 3.0; + addy = 0.02 * (x2 - x1); + if (addy > 3.0) + addy = 3.0; + x1 += addx; + x2 -= addx; + +/*fixme: to simplify*/ + if (k1->staff == upstaff) + y1 += s * addy; + else y1 = -6.; + if (k2->staff == upstaff) + y2 += s * addy; + else y2 = -6.; + + a = (y2 - y1) / (x2 - x1); /* slur steepness */ + if (a > SLUR_SLOPE) + a = SLUR_SLOPE; + else if (a < -SLUR_SLOPE) + a = -SLUR_SLOPE; + if (a * s > 0) + y1 = y2 - a * (x2 - x1); + else y2 = y1 + a * (x2 - x1); + + /* for big vertical jump, shift endpoints */ + y = y2 - y1; + if (y > 8) + y = 8; + else if (y < -8) + y = -8; + z = y; + if (z < 0) + z = -z; + dx = 0.5 * z; + dy = 0.3 * y; + if (y * s > 0) { + x2 -= dx; + y2 -= dy; + } else { + x1 += dx; + y1 += dy; + } + + /* special case for grace notes */ + if (k1->un.note.grace) + x1 = k1->x - GSTEM_XOFF * 0.5; + if (k2->un.note.grace) + x2 = k2->x + GSTEM_XOFF * 1.5; + + h = 0; + a = (y2 - y1) / (x2 - x1); + addy = y1 - a * x1; + for (k = k1->nxt; k != 0 && k != k2 ; k = k->nxt) { + if (k->staff != upstaff) + continue; + switch (k->type) { + case NOTE: + case REST: + if (s > 0) { + y = k->ymx + 6; + if (y < k->ys + 2) + y = k->ys + 2; + y -= a * k->x + addy; + if (y > h) + h = y; + } else { + y = k->ymn - 6; + if (y > k->ys - 2) + y = k->ys - 2; + y -= a * k->x + addy; + if (y < h) + h = y; + } + break; + case GRACE: { + struct SYMBOL *g; + + for (g = k->grace; g != 0; g = g->nxt) { + y = g->y - a * k->x - addy; + if (s > 0) { + y += GSTEM + 2; + if (y > h) + h = y; + } else { + y -= 2; + if (y < h) + h = y; + } + } + break; + } + } + } + + y1 += 0.45 * h; + y2 += 0.45 * h; + h *= 0.65; + + if (nn > 3) + height = (0.08 * (x2 - x1) + 12.) * s; + else height = (0.03 * (x2 - x1) + 8.) * s; + if (s > 0) { + if (height < 3 * h) + height = 3 * h; + if (height > 40) + height = 40; + } else { + if (height > 3 * h) + height = 3 * h; + if (height < -40) + height = -40; + } + + y = y2 - y1; + if (y < 0) + y = -y; + if (s > 0) { + if (height < 0.8 * y) + height = 0.8 * y; + } else { + if (height > -0.8 * y) + height = -0.8 * y; + } + height *= cfmt.slurheight; + + output_slur(x1, y1, x2, y2, s, + height, upstaff); + + /* have room for other symbols */ + a = (y2 - y1) / (x2 - x1); +/*---fixme: it seems to work with 0.4, but why?*/ + addy = y1 - a * x1 + 0.4 * height; + for (k = k1; ; k = k->nxt) { + if (k == k2 + && k->un.note.slur_st) + break; /* the next slur will set the top/bottom */ + if (k->staff == upstaff) { + y = a * k->x + addy; + if (k->dc_top < y) + k->dc_top = y; + else if (k->dc_bot > y) + k->dc_bot = y; + } + if (k == k2) + break; + } +} + +/* -- find place to terminate/start slur -- */ +static struct SYMBOL *next_scut(struct SYMBOL *s) +{ + struct SYMBOL *prev; + + prev = s; + for (s = s->nxt; s != 0; s = s->nxt) { + if (s->type == BAR + && (s->un.bar.type == B_RREP + || s->un.bar.type == B_DREP + || s->un.bar.type == B_THIN_THICK + || s->un.bar.type == B_THICK_THIN + || (s->un.bar.repeat_bar + && s->text != 0 + && s->text[0] != '1'))) + return s; + prev = s; + } + /*fixme: KO when no note for this voice at end of staff */ + return prev; +} + +static struct SYMBOL *prev_scut(struct SYMBOL *s) +{ + struct SYMBOL *sym; + int voice; + + voice = s->voice; + for ( ; s != 0; s = s->prv) { + if (s->type == BAR + && (s->un.bar.type == B_RREP + || s->un.bar.type == B_DREP + || s->un.bar.type == B_THIN_THICK + || s->un.bar.type == B_THICK_THIN + || (s->un.bar.repeat_bar + && s->text != 0 + && s->text[0] != '1'))) + return s; + } + + /* return sym before first note/rest/bar */ + sym = voice_tb[voice].sym; + for (s = sym; s != 0; s = s->nxt) { + if (s->len > 0 /* if note or rest */ + || s->type == BAR) + return s->prv; + } + return sym; +} + +/* -- draw the ties between two notes/chords -- */ +static void draw_note_ties(struct SYMBOL *k1, + struct SYMBOL *k2, + int nslur, + int *mhead1, + int *mhead2, + int job) +{ + int i, s0, s1, s; + float x1, x2, height; + + s1 = 0; + if ((s0 = k1->multi) == 0 + && (s0 = k2->multi) == 0) + s1 = slur_direction(k1, k2); + for (i = 0; i < nslur; i++) { + int m1, m2, p1, p2, y1, y2; + + m1 = mhead1[i]; + p1 = k1->pits[m1]; + m2 = mhead2[i]; + p2 = k2->pits[m2]; + if ((s = s0) == 0) { + + /* try to have the same tie direction as the next one */ + if (job != 2 + && k2->nhd != 0 + && m2 == k2->nhd + && k2->un.note.ti1[m2]) + s = 1; + else if (k1->nhd == 0) + s = s1; + else if (m1 == 0) /* if bottom */ + s = -1; + else if (m1 == k1->nhd) /* if top */ + s = 1; + else s = s1; + } + + x1 = k1->x + k1->shhd[m1]; + x2 = k2->x + k2->shhd[m2]; + if (job == 2) { /* half slurs from last note in line */ + p2 = p1; + x2 -= k2->wl; + if (k1 == k2) + x2 = realwidth - 2; + } else if (job == 1) { /* half slurs to first note in line */ + p1 = p2; +/* if (k1 == k2->prv) { */ + x1 = k1->x + k1->wr; + if (x1 > x2 - 20) + x1 = x2 - 20; +/* } */ + } + if (x2 - x1 > 20) { + x1 += 2; + x2 -= 2; + } + y1 = 3 * (p1 - 18) + 2 * s; + y2 = 3 * (p2 - 18) + 2 * s; + if (k1->nhd != 0) + x1 += 4.5; + else y1 += ((p1 % 2) ? 3 : 2) * s; + if (k2->nhd != 0) + x2 -= 4.5; + else y2 += ((p2 % 2) ? 3 : 2) * s; + if (s > 0) { + if (k1->nflags > -2 && k1->stem > 0 + && k1->nhd == 0) + x1 += 4.5; + if (!(p1 % 2) && k1->dots > 0) + y1 = 3 * (p1 - 18) + 6; + } else /*if (s < 0)*/ { + if (k2->nflags > -2 && k2->stem < 0 + && k2->nhd == 0) + x2 -= 4.5; + } + + /* tie between 2 staves */ + if (k1->staff != k2->staff) { + s = k1->staff - k2->staff; + y1 = 3 * (p1 - 18) + 3 * s; + y2 = 3 * (p2 - 18) - 3 * s; + x1 += 4; + x2 -= 4; + PUT4("%.1f %.1f M %.1f %.1f lineto stroke\n", + x1, staff_tb[k1->staff].y + y1, + x2, staff_tb[k2->staff].y + y2); + continue; + } + + height = (0.04 * (x2 - x1) + 8) * s; + output_slur(x1, staff_tb[k1->staff].y + y1, + x2, staff_tb[k1->staff].y + y2, + s, height, -1); + } +} + +/* -- draw ties between neighboring notes/chords -- */ +static void draw_ties(struct SYMBOL *k1, + struct SYMBOL *k2, + int job) +{ + int i, j, m1; + int mhead1[MAXHD], mhead2[MAXHD], nslur, nh1, nh2; + + nbuf_check(); + + nslur = 0; + nh1 = k1->nhd; + + if (job == 2) { /* half slurs from last note in line */ + for (i = 0; i <= nh1; i++) { + if (k1->un.note.ti1[i]) { + mhead1[nslur] = i; + nslur++; + } + j = i + 1; + for (m1 = 0; m1 <= nh1; m1++) { + if (k1->un.note.sl1[m1] == j) { + mhead1[nslur] = m1; + nslur++; + break; + } + } + } + if (nslur > 0) + draw_note_ties(k1, k2, + nslur, mhead1, mhead1, job); + return; + } + + if (job == 1) { /* half slurs to first note in line */ + /* (ti2 is just used in this case) */ + for (i = 0; i <= nh1; i++) { + if (k2->un.note.ti2[i]) { + mhead1[nslur] = i; + nslur++; + } + j = i + 1; + for (m1 = 0; m1 <= nh1; m1++) { + if (k2->un.note.sl2[m1] == j) { + mhead1[nslur] = m1; + nslur++; + break; + } + } + } + if (nslur > 0) + draw_note_ties(k1, k2, + nslur, mhead1, mhead1, job); + return; + } + + /* real 2-note case: set up list of slurs/ties to draw */ + nh2 = k2->nhd; + for (i = 0; i <= nh1; i++) { + int m2; + + if ((m2 = k1->un.note.ti1[i]) != 0) { + mhead1[nslur] = i; + mhead2[nslur] = m2 - 1; + nslur++; + } + j = i + 1; + for (m1 = 0; m1 <= nh1; m1++) { + if (k1->un.note.sl1[m1] == j) { + for (m2 = 0; m2 <= nh2; m2++) { + if (k2->un.note.sl2[m2] == j) { + mhead1[nslur] = m1; + mhead2[nslur] = m2; + nslur++; + break; + } + } + } + } + } + if (nslur > 0) + draw_note_ties(k1, k2, + nslur, mhead1, mhead2, job); +} + +/* -- draw all slurs/ties between neighboring notes -- */ +static void draw_all_ties(struct SYMBOL *sym) +{ + struct SYMBOL *s1, *s2; + + for (s1 = sym; s1 != 0; s1 = s1->nxt) + if (s1->type != CLEF && s1->type != KEYSIG + && s1->type != TIMESIG) + break; + for (s2 = s1; s2 != 0; s2 = s2->nxt) { + if (s2->type == NOTE) + break; + } + if (s2 == 0) + return; + draw_ties(s1, s2, 1); /* 1st note */ + + for (;;) { + s1 = s2; /* keep the last note */ + for (s2 = s2->nxt; s2 != 0; s2 = s2->nxt) { + if (s2->type == NOTE) + break; + if (s2->type == BAR + && (s2->un.bar.type == B_RREP + || s2->un.bar.type == B_DREP + || s2->un.bar.type == B_THIN_THICK + || s2->un.bar.type == B_THICK_THIN + || (s2->un.bar.repeat_bar + && s2->text != 0 + && s2->text[0] != '1'))) + break; + } + if (s2 == 0) + break; + draw_ties(s1, s2, s2->type == NOTE ? 0 : 2); + if (s2->type != NOTE) { + for (s2 = s2->nxt; s2 != 0; s2 = s2->nxt) { + if (s2->type == NOTE) + break; + } + if (s2 == 0) + break; + } + } + s2 = next_scut(s1); + if (s2->type==NOTE) // this is too rigorous + draw_ties(s1, s2, 2); +} + +/* -- draw all phrasing slurs for one staff -- */ +/* (the staves are not yet defined) */ +static void draw_all_slurs(struct SYMBOL *sym) +{ + struct SYMBOL *s, *s1, *k; + struct SYMBOL *cut, *gr1, *gr2; + int pass, num, gr1_out; + + for (pass = 0; ; pass++) { + num = 0; + gr1 = gr2 = 0; + s = sym; + for (;;) { + if (s == 0) { + if (gr1 == 0 + || (s = gr1->nxt) == 0) + break; + gr1 = 0; + } + if (s->grace != 0) { + gr1 = s; + s = s->grace; + continue; + } + if ((s->type != NOTE + && s->type != REST) + || !s->un.note.slur_st) { + s = s->nxt; + continue; + } + k = 0; /* find matching slur end */ + s1 = s->nxt; + gr1_out = 0; + for (;;) { + if (s1 == 0) { + if (gr2 != 0) { + s1 = gr2->nxt; + gr2 = 0; + continue; + } + if (gr1 == 0 || gr1_out) + break; + s1 = gr1->nxt; + gr1_out = 1; + continue; + } + if (s1->grace != 0) { + gr2 = s1; + s1 = s1->grace; + continue; + } + if (s1->type != NOTE + && s1->type != REST) { + s1 = s1->nxt; + continue; + } + if (s1->un.note.slur_end) { + k = s1; + break; + } + if (s1->un.note.slur_st) + break; + s1 = s1->nxt; + } + if (k == 0) { + s = s->nxt; + continue; + } + + /* if slur in grace note sequence, change the linkages */ + if (gr1 != 0) { + for (s1 = s; s1->nxt != 0; s1 = s1->nxt) + ; + s1->nxt = gr1->nxt; + gr1->nxt->prv = s1; + gr1->un.note.slur_st = 1; + } + if (gr2 != 0) { + gr2->prv->nxt = gr2->grace; + gr2->grace->prv = gr2->prv; + gr2->un.note.slur_st = 1; + } + + s->un.note.slur_st--; + k->un.note.slur_end--; + cut = next_scut(s); + if (cut->time <= k->time) + for (s1 = cut->nxt; s1 != 0; s1 = s1->nxt) { + if (s1 == k) { + draw_slur(s, cut); + s = prev_scut(k); + break; + } + } + draw_slur(s, k); + num++; + + /* if slur in grace note sequence, restore the linkages */ + if (gr1 != 0) { + gr1->nxt->prv->nxt = 0; + gr1->nxt->prv = gr1; + } + if (gr2 != 0) { + gr2->prv->nxt = gr2; + gr2->grace->prv = 0; + } + + s = s->nxt; + } + if (num == 0) + break; + } + + /* do unbalanced slurs still left over */ + for (s = sym; s != 0; s = s->nxt) { + if (s->type != NOTE + && s->type != REST) + continue; + if (s->un.note.slur_end) { + cut = prev_scut(s); + draw_slur(cut, s); + } + if (s->un.note.slur_st) { + cut = next_scut(s); + draw_slur(s, cut); +/*fixme: a slur may end in the row after the next one */ + } + } +} + +/* -- draw the lyrics under notes -- */ +/* !! this routine is tied to set_width() and set_staff() !! */ +static void draw_vocals(struct SYMBOL *sym) +{ + int hyflag, l, j, lflag, nwl; + float lastx, vfsize, w, swfac, lskip; + char t[81]; + float y; + int curfont; + + if ((nwl = voice_tb[sym->voice].nvocal) == 0) + return; + y = voice_tb[sym->voice].yvocal; + curfont = -1; /* (force new font) */ + lskip = swfac = 0; /* (compiler warning) */ + for (j = 0; j < nwl; j++) { + struct SYMBOL *s; + float x0, shift; + + hyflag = lflag = 0; + s = sym->nxt; /* keysig */ + lastx = s->x + s->wr; + x0 = 0; /* (compiler warning) */ + for (s = sym; s != 0; s = s->nxt) { + struct lyrics *ly; + const char *p; + + if ((ly = s->ly) == 0 + || (p = ly->w[j]) == 0) { + switch (s->type) { + case REST: + case MREST: + case MREP: + if (lflag) { + PUT3("%.1f %.1f %.1f wln ", + x0 - lastx, lastx + 3, y); + lflag = 0; + lastx = s->x + s->wr; + } + } + continue; + } + if (*p++ != curfont) { /* font change (see get_lyric) */ + curfont = p[-1]; + vfsize = lyric_fonts[curfont].size; + lskip = 1.1 * vfsize; + swfac = cfmt.vocalfont.swfac * vfsize; + PUT2("%.1f F%d ", + vfsize, lyric_fonts[curfont].font); + } + tex_str(t, p, sizeof t, &w); + if (isdigit(t[0])) + shift = LYDIG_SH * swfac * cwid('1'); + else if (t[0] == '\x03') /* '_' */ + shift = 0; + else { + shift = swfac * (w + 2 * cwid(' ')) * VOCPRE; + if (shift > 20.) + shift = 20.; + } + if (hyflag + && t[0] == '\x03') /* '_' */ + t[0] = '\x02'; + if (hyflag + && t[0] != '\x02') { /* not '-' */ + vfsize = s->x - shift - lastx; + l = (int) vfsize / 40 + 1; + vfsize /= l; + x0 = lastx + vfsize * 0.5 - 1.5; + while (--l >= 0) { + PUT2("%.1f %.1f whf ", x0, y); + x0 += vfsize; + } + hyflag = 0; + lastx = s->x + s->wr; + } + if (lflag + && t[0] != '\x03') { /* not '_' */ + PUT3("%.1f %.1f %.1f wln ", + x0 - lastx + 3., lastx + 3., y); + lflag = 0; + lastx = s->x + s->wr; + } + + x0 = s->x - shift; + if (t[0] == '\x02' /* '-' */ + || t[0] == '\x03') { /* '_' */ + if (t[0] == '\x02') + hyflag = 1; + else lflag = 1; + continue; + } + l = strlen(t) - 1; + if (t[l] == '\x02') { /* '-' at end */ + t[l] = '\0'; + hyflag = 1; + } + PUT3("%.1f %.1f M (%s) show ", x0, y, t); + lastx = x0 + swfac * w; + } + if (hyflag) { + vfsize = realwidth - 10. - lastx; + l = (int) vfsize / 40 + 1; + vfsize /= l; + x0 = lastx + vfsize * 0.5 - 1.5; + while (--l >= 0) { + PUT2("%.1f %.1f whf ", x0, y); + x0 += vfsize; + } + } + if (lflag) + PUT3("%.1f %.1f %.1f wln", + x0 - lastx + 3., lastx + 3., y); + PUT0("\n"); + y -= lskip; + } +} + +/* -- draw the symbols near the notes -- */ +/* (the staves are not yet defined) */ +/* order: + * - beams + * - decorations near the notes + * - n-plets + * - slurs + * - decorations tied to the notes + * - guitar chords, then remaining decorations + */ +void draw_sym_near(void) +{ + struct VOICE_S *p_voice; + struct SYMBOL *s; + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + struct BEAM bm; + + bm.s2 = 0; + bm.staff = 0; + for (s = p_voice->sym; s != 0; s = s->nxt) { + if (s->type == NOTE + && (s->sflags & S_WORD_ST) + && !s->un.note.word_end) { + if (calculate_beam(&bm, s)) + draw_beams(&bm); + if (s == bm.s2) + bm.s2 = 0; + } + } + } + + draw_deco_near(); + + /* have room for the accidentals and adjust the grace notes x offsets */ + for (s = first_voice->sym; s != 0; s = s->ts_next) { + int nhd; + float x, y; + struct SYMBOL *gr; + + if (s->type != NOTE) { + if ((gr = s->grace) == 0) + continue; + for (gr = s->grace; gr->nxt != 0; gr = gr->nxt) + ; + x = s->x - gr->x; + for (gr = s->grace; gr != 0; gr = gr->nxt) + gr->x += x; + continue; + } + nhd = s->un.note.nhd; + if (s->un.note.accs[nhd]) { + y = s->y + 8; + if (s->dc_top < y) + s->dc_top = y; + } + if (s->un.note.accs[0]) { + y = s->y; + if (s->un.note.accs[0] == A_SH + || s->un.note.accs[0] == A_NT) + y -= 7; + else y -= 5; + if (s->dc_bot > y) + s->dc_bot = y; + } + } + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + draw_nplet_brackets(p_voice->sym); + + draw_deco_note(); + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + draw_all_slurs(p_voice->sym); + + draw_deco_staff(); +} + +/* -- draw remaining symbols when the staves are defined -- */ +void draw_symbols(struct VOICE_S *p_voice) +{ + struct SYMBOL *sym; + float x, y; + struct BEAM bm; + struct SYMBOL *s; + + sym = p_voice->sym; + + /* draw the symbols */ + bm.s2 = 0; + bm.staff = -1; /* staves defined */ + for (s = sym; s != 0; s = s->nxt) { + nbuf_check(); + x = s->x; + switch (s->type) { + case NOTE: + if ((s->sflags & S_WORD_ST) + && !s->un.note.word_end) { + if (calculate_beam(&bm, s)) + draw_beams(&bm); + } + draw_note(x, s, bm.s2 == 0); + if (s == bm.s2) + bm.s2 = 0; + break; + case REST: + draw_rest(s); + break; + case BAR: + if (p_voice != first_voice) + break; + draw_bar(x, s); + break; + case CLEF: { + int staff; + char ct = 't'; /* clef type - def: treble */ + + if (p_voice->second) + break; /* only one clef per staff */ + staff = s->staff; + memcpy(&staff_tb[staff].clef, &s->un.clef, /* (for next lines) */ + sizeof s->un.clef); + if (s->un.clef.invis) + break; + y = staff_tb[staff].y; + + switch (s->un.clef.type) { + case BASS: + ct = 'b'; + y += (float) ((s->un.clef.line - 4) * 6); + break; + case ALTO: + ct = 'c'; + y += (float) ((s->un.clef.line - 3) * 6); + break; + case TREBLE: + y += (float) ((s->un.clef.line - 2) * 6); + break; + case PERC: + ct = 'p'; + break; + default: + bug("unknown clef type", 0); + } + PUT4("%.1f %.1f %c%cclef\n", x, y, + s->u ? 's' : ' ', ct); + if (s->un.clef.octave == 0) + break; +/*fixme: works fine for treble clef only*/ + PUT3("%.1f %.1f oct%c\n", x, y, + s->un.clef.octave > 0 ? 'u' : 'l'); + break; + } + case TIMESIG: + memcpy(&p_voice->meter, &s->un.meter, + sizeof p_voice->meter); + if (p_voice != first_voice) + break; + draw_timesig(x, s); + break; + case KEYSIG: + draw_keysig(p_voice, x, s); + break; + case MREST: + if (p_voice->second) + break; + PUT3("(%d) %.1f %.1f mrest\n", + s->un.bar.len, + x, staff_tb[s->staff].y); + break; + case MREP: + if (p_voice->second) + break; + if (s->un.bar.len == 1) { + x = (s->prv->x + s->nxt->x) * 0.5; + PUT2("%.1f %.1f mrep\n", + x, staff_tb[s->staff].y); + break; + } + PUT2("%.1f %.1f mrep2\n", + s->prv->x, staff_tb[s->staff].y); + if (s->voice != first_voice - voice_tb) + break; + set_font(&cfmt.gchordfont); + PUT3("%.1f %.1f M (%d) cshow\n", + s->prv->x, staff_tb[s->staff].y + 24 + 4, + s->un.bar.len); + break; + case GRACE: + draw_gracenotes(x, s); + break; + case TEMPO: + case STAVES: + case PART: + break; /* nothing */ + case FMTCHG: + if (s->u == STBRK) { + float dx, dy; + + if (p_voice != first_voice) + break; + dx = s->ts_prev->x - x; + if (s->ts_prev->type == BAR) + dx += 1; + else dx += s->ts_prev->wr; + y = staff_tb[nstaff].y - 1.; + dy = staff_tb[0].y + 24. + 2. - y; + PUT4("currentgray 1.0 setgray" + " %.1f %.1f %.1f %.1f M 2 copy" + " 0 exch RL 0 RL" + " 0 exch neg RL neg 0 RL fill" + " setgray\n", + dx, dy, x, y); + if (nstaff != 0 && s->xmx > 0.5 * CM) + draw_lstaff(x); + break; + } + if (s->u == PSSEQ) { + PUT1("%s\n", &s->text[13]); + break; + } +#if 1 +/*fixme: should remove the other format changes */ + break; +#else + /* fall thru */ +#endif + default: + bug("Symbol not drawn", 1); + } + } + + draw_all_ties(sym); + + draw_vocals(sym); +} + +/* -- work out accidentals to be applied to each note -- */ +void setmap(int sf, /* number of sharps/flats in key sig (-7 to +7) */ + char *map) /* for 7 notes only */ +{ + int j; + + for (j = 7; --j >= 0; ) + map[j] = A_NULL; + switch (sf) { + case 7: map[6] = A_SH; + case 6: map[2] = A_SH; + case 5: map[5] = A_SH; + case 4: map[1] = A_SH; + case 3: map[4] = A_SH; + case 2: map[0] = A_SH; + case 1: map[3] = A_SH; + break; + case -7: map[3] = A_FT; + case -6: map[0] = A_FT; + case -5: map[4] = A_FT; + case -4: map[1] = A_FT; + case -3: map[5] = A_FT; + case -2: map[2] = A_FT; + case -1: map[6] = A_FT; + break; + } +} + +/* -- draw the tin whistle tablature -- */ +void draw_whistle(void) +{ + struct VOICE_S *p_voice; + struct SYMBOL *s; + int i, j, pitch, w_pitch, w_octave; + int sf; + char workmap[70]; /* sharps/flats - base: lowest 'C' */ + char basemap[7]; + static char pitnam[12 * 2 + 1] = "C\0C#D\0EbE\0F\0F#G\0AbA\0BbB\0"; + static int w_tb[12] = { + 0x222222, 0x122222, 0x022222, 0x012222, 0x002222, 0x000222, + 0x000122, 0x000022, 0x000012, 0x000002, 0x000220, 0x000000 + }; + static int scale[7] = {0, 2, 4, 5, 7, 9, 11}; /* index = natural note */ + static int acc_pitch[6] = {0, 1, 0, -1, 2, -2}; /* index = enum accidentals */ + + sf = 0; + for (i = 0; i < nwhistle; i++) { + p_voice = &voice_tb[whistle_tb[i].voice]; + if (p_voice != first_voice + && p_voice->prev == 0) + continue; + w_pitch = whistle_tb[i].pitch; + PUT1("(%.2s) tw_head\n", &pitnam[(w_pitch % 12) * 2]); + for (s = p_voice->sym; s != 0; s = s->nxt) { + switch (s->type) { + case NOTE: + break; + case KEYSIG: + sf = s->un.key.sf; + setmap(sf, basemap); + for (j = 0; j < 10; j++) + memcpy(&workmap[7 * j], + basemap, 7); + continue; + case BAR: + if (s->un.bar.type == B_INVIS) + continue; + for (j = 0; j < 10; j++) + memcpy(&workmap[7 * j], + basemap, 7); + continue; + default: + continue; + } + if (s->un.note.ti2[0] != 0) /* tied note */ + continue; + pitch = s->un.note.pits[0] + 19; + if (s->un.note.accs[0] != 0) { + workmap[pitch] = s->un.note.accs[0] == A_NT + ? A_NULL + : s->un.note.accs[0]; + } + pitch = scale[pitch % 7] + + acc_pitch[workmap[pitch]] + + 12 * (pitch / 7); + w_octave = 0; + pitch -= w_pitch; + while (pitch < 0) { + pitch += 12; + w_octave--; + } + while (pitch >= 36) { + pitch -= 12; + w_octave++; + } + PUT1("%.2f 0 ", s->x); + if (w_octave > 0) + PUT0("tw_over "); + else if (w_octave < 0) + PUT0("tw_under "); + w_octave = pitch / 12; + if (pitch == 12) + pitch = 0x222220; /* only special case (?) */ + else pitch = w_tb[pitch % 12]; + for (j = 1; j < 7; j++) { + PUT1("tw_%d ", pitch & 0x0f); + pitch >>= 4; + } + if (w_octave == 0) + PUT0("pop pop"); + else if (w_octave == 1) + PUT0("tw_p"); + else PUT0("tw_pp"); + PUT0("\n"); + } + bskip(63.); + } +} diff --git a/src-abcm2ps/fbook.fmt b/src-abcm2ps/fbook.fmt new file mode 100644 index 0000000..ce75d95 --- /dev/null +++ b/src-abcm2ps/fbook.fmt @@ -0,0 +1,26 @@ + +% Emulates the Jazz Fakebook style. +% Use caps for title, parts or Q: for tempo. + + scale 0.70 + topmargin 1.5cm + titlefont Helvetica-Bold 13 + subtitlefont Helvetica-Bold 10 + titleleft false + titlecaps + composerfont Helvetica 9 + composerspace 0.3cm + partsfont Times-Bold 10 + vocalfont Times-Bold 13 + musicspace 0.7cm + gchordfont Times-Roman 12 + parskipfac 1.0 + leftmargin 1.3cm + staffwidth 18.4cm + staffsep 45 + maxshrink 0.65 + + lineskipfac 1.1 + parskipfac 0 + textspace 0.2cm + textfont Times-Roman 10 diff --git a/src-abcm2ps/format.cpp b/src-abcm2ps/format.cpp new file mode 100644 index 0000000..479965a --- /dev/null +++ b/src-abcm2ps/format.cpp @@ -0,0 +1,481 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +struct FORMAT cfmt; /* current format for output */ + +static char *fontnames[MAXFONTS]; /* list of font names */ +static char used_font[MAXFONTS]; /* used fonts */ +static int nfontnames; +static float staffwidth; + +enum { + FORMAT_I, /* int */ + FORMAT_R, /* float */ + FORMAT_F, /* font spec */ + FORMAT_U, /* float with unit */ + FORMAT_B, /* boolean */ + FORMAT_S /* string */ +}; + +/* format table */ +static struct format { + const char *name; + short type; + short subtype; /* special cases - see code */ + void *v; +} format_tb[] = { + {"autoclef", FORMAT_B, 0, &cfmt.autoclef}, + {"barsperstaff", FORMAT_I, 0, &cfmt.barsperstaff}, + {"botmargin", FORMAT_U, 0, &cfmt.botmargin}, + {"composerfont", FORMAT_F, 0, &cfmt.composerfont}, + {"composerspace", FORMAT_U, 0, &cfmt.composerspace}, + {"continueall", FORMAT_B, 0, &cfmt.continueall}, + {"encoding", FORMAT_I, 1, &cfmt.encoding}, + {"exprabove", FORMAT_B, 0, &cfmt.exprabove}, + {"exprbelow", FORMAT_B, 0, &cfmt.exprbelow}, + {"footer", FORMAT_S, 0, &cfmt.footer}, + {"footerfont", FORMAT_F, 0, &cfmt.footerfont}, + {"freegchord", FORMAT_B, 0, &cfmt.freegchord}, + {"flatbeams", FORMAT_B, 0, &cfmt.flatbeams}, + {"gchordbox", FORMAT_B, 0, &cfmt.gchordbox}, + {"gchordfont", FORMAT_F, 3, &cfmt.gchordfont}, + {"graceslurs", FORMAT_B, 0, &cfmt.graceslurs}, + {"header", FORMAT_S, 0, &cfmt.header}, + {"headerfont", FORMAT_F, 0, &cfmt.headerfont}, + {"indent", FORMAT_U, 0, &cfmt.indent}, + {"infofont", FORMAT_F, 0, &cfmt.infofont}, + {"infoline", FORMAT_B, 0, &cfmt.infoline}, + {"infospace", FORMAT_U, 0, &cfmt.infospace}, + {"landscape", FORMAT_B, 0, &cfmt.landscape}, + {"leftmargin", FORMAT_U, 0, &cfmt.leftmargin}, + {"lineskipfac", FORMAT_R, 0, &cfmt.lineskipfac}, + {"maxshrink", FORMAT_R, 0, &cfmt.maxshrink}, + {"measurebox", FORMAT_B, 0, &cfmt.measurebox}, + {"measurefirst", FORMAT_I, 2, &cfmt.measurefirst}, + {"measurefont", FORMAT_F, 2, &cfmt.measurefont}, + {"measurenb", FORMAT_I, 0, &cfmt.measurenb}, + {"musiconly", FORMAT_B, 0, &cfmt.musiconly}, + {"musicspace", FORMAT_U, 0, &cfmt.musicspace}, + {"notespacingfactor", FORMAT_R, 1, &cfmt.notespacingfactor}, + {"oneperpage", FORMAT_B, 0, &cfmt.oneperpage}, + {"pageheight", FORMAT_U, 0, &cfmt.pageheight}, + {"pagewidth", FORMAT_U, 0, &cfmt.pagewidth}, + {"parskipfac", FORMAT_R, 0, &cfmt.parskipfac}, + {"partsbox", FORMAT_B, 0, &cfmt.partsbox}, + {"partsfont", FORMAT_F, 1, &cfmt.partsfont}, + {"partsspace", FORMAT_U, 0, &cfmt.partsspace}, + {"printparts", FORMAT_B, 0, &cfmt.printparts}, + {"printtempo", FORMAT_B, 0, &cfmt.printtempo}, + {"repeatfont", FORMAT_F, 0, &cfmt.repeatfont}, + {"rightmargin", FORMAT_U, 0, &cfmt.rightmargin}, + {"scale", FORMAT_R, 0, &cfmt.scale}, + {"slurheight", FORMAT_R, 0, &cfmt.slurheight}, + {"splittune", FORMAT_B, 0, &cfmt.splittune}, + {"squarebreve", FORMAT_B, 0, &cfmt.squarebreve}, + {"staffsep", FORMAT_U, 0, &cfmt.staffsep}, + {"staffwidth", FORMAT_U, 1, &staffwidth}, + {"straightflags", FORMAT_B, 0, &cfmt.straightflags}, + {"stretchlast", FORMAT_B, 0, &cfmt.stretchlast}, + {"stretchstaff", FORMAT_B, 0, &cfmt.stretchstaff}, + {"subtitlefont", FORMAT_F, 0, &cfmt.subtitlefont}, + {"subtitlespace", FORMAT_U, 0, &cfmt.subtitlespace}, + {"sysstaffsep", FORMAT_U, 0, &cfmt.sysstaffsep}, + {"tempofont", FORMAT_F, 0, &cfmt.tempofont}, + {"textfont", FORMAT_F, 0, &cfmt.textfont}, + {"textspace", FORMAT_U, 0, &cfmt.textspace}, + {"titlecaps", FORMAT_B, 0, &cfmt.titlecaps}, + {"titlefont", FORMAT_F, 0, &cfmt.titlefont}, + {"titleleft", FORMAT_B, 0, &cfmt.titleleft}, + {"titlespace", FORMAT_U, 0, &cfmt.titlespace}, + {"topmargin", FORMAT_U, 0, &cfmt.topmargin}, + {"topspace", FORMAT_U, 0, &cfmt.topspace}, + {"vocalabove", FORMAT_B, 0, &cfmt.vocalabove}, + {"vocalfont", FORMAT_F, 0, &cfmt.vocalfont}, + {"vocalspace", FORMAT_U, 0, &cfmt.vocalspace}, + {"withxrefs", FORMAT_B, 0, &cfmt.withxrefs}, + {"wordsfont", FORMAT_F, 0, &cfmt.wordsfont}, + {"wordsspace", FORMAT_U, 0, &cfmt.wordsspace}, + {"writehistory", FORMAT_B, 0, &cfmt.writehistory}, + {0, 0, 0, 0} /* end of table */ +}; + +/* subroutines connected with page layout */ + +/* -- add a font -- */ +static int add_font(const char *fname) +{ + int fnum; + + for (fnum = nfontnames; --fnum >= 0; ) + if (strcmp(fname, fontnames[fnum]) == 0) + return fnum; /* already there */ + + if (nfontnames >= MAXFONTS) { + alert( "Too many fonts\n"); + return 0; + } + if (file_initialized) + alert("Cannot have a new font when the output file is opened"); + fnum = nfontnames++; + fontnames[fnum] = strdup(fname); + strcpy(fontnames[fnum], fname); + return fnum; +} + +/* -- fontspec -- */ +static void fontspec(struct FONTSPEC *f, + const char *name, + float size) +{ + if (name != 0) + f->fnum = add_font(name); + else name = fontnames[f->fnum]; + f->size = size; + f->swfac = 1.0; + if (strcmp(name, "Times-Bold") == 0) + f->swfac = 1.05; + else if (strcmp(name, "Helvetica-Bold") == 0) + f->swfac = 1.15; + else if (strstr(name, "Helvetica") + || strstr(name,"Palatino")) + f->swfac = 1.10; +} + +/* -- output the font definitions -- */ +void define_fonts(void) +{ + int i; + + for (i = 0; i < nfontnames; i++) { + if (used_font[i]) + define_font(fontnames[i], i); + } +} + +/* -- mark the used fonts -- */ +void make_font_list(void) +{ + struct FORMAT *f; + + f = &cfmt; + used_font[f->titlefont.fnum] = 1; + used_font[f->subtitlefont.fnum] = 1; + used_font[f->composerfont.fnum] = 1; + used_font[f->partsfont.fnum] = 1; + used_font[f->vocalfont.fnum] = 1; + used_font[f->textfont.fnum] = 1; + used_font[f->tempofont.fnum] = 1; + used_font[f->wordsfont.fnum] = 1; + used_font[f->gchordfont.fnum] = 1; + used_font[f->infofont.fnum] = 1; + used_font[f->footerfont.fnum] = 1; + used_font[f->headerfont.fnum] = 1; + used_font[f->repeatfont.fnum] = 1; + used_font[f->measurefont.fnum] = 1; +} + +/* -- set the default format -- */ +void set_format(void) +{ + struct FORMAT *f; + + f = &cfmt; + memset(f, 0, sizeof *f); + f->pageheight = PAGEHEIGHT; + f->pagewidth = PAGEWIDTH; + f->leftmargin = 1.0 * CM; // 1.8 * CM; + f->rightmargin = 1.3 * CM; // 1.8 * CM; + f->topmargin = 0.5 * CM; // 1.0 * CM; + f->botmargin = 0.5 * CM; // 1.0 * CM; + f->topspace = 0.5 * CM; // 0.8 * CM; + f->titlespace = 0.2 * CM; + f->subtitlespace = 0.1 * CM; + f->composerspace = 0.1 * CM; // 0.2 * CM; + f->musicspace = 0.2 * CM; + f->partsspace = 0.1 * CM; // 0.3 * CM; + f->staffsep = 43.0 * PT; // 46.0 * PT; + f->sysstaffsep = 34.0 * PT; + f->vocalspace = 18.0 * PT; // 23.0 * PT; + f->textspace = 0.2 * CM; // 0.5 * CM; + f->wordsspace = 0.; + f->scale = 0.65; // 0.75; + f->slurheight = 1.0; + f->maxshrink = 0.9; // 0.65; + f->stretchstaff = 1; + f->graceslurs = 1; + f->lineskipfac = 1.1; + f->parskipfac = 0.3; // 0.4; + f->measurenb = -1; + f->measurefirst = 1; + f->printparts = 1; + f->printtempo = 1; + f->autoclef = 1; + f->notespacingfactor = 1.414; + f->gchordbox=1; // 0; + f->continueall=1; + fontspec(&f->titlefont, "Helvetica-Bold", 20.0); + fontspec(&f->subtitlefont, "Helvetica-Bold", 10.0); + fontspec(&f->composerfont, "Times-Italic", 12.0); + fontspec(&f->partsfont, "Times-Bold", 10.0); + fontspec(&f->tempofont, "Times-Bold", 15.0); + fontspec(&f->vocalfont, "Times-Bold", 13.0); + fontspec(&f->textfont, "Times-Roman", 10.0); + fontspec(&f->wordsfont, "Times-Roman", 10.0); + fontspec(&f->gchordfont, "Times-Roman", 16.0); + fontspec(&f->infofont, "Times-Italic", 12.0); /* same as composer by default */ + fontspec(&f->footerfont, "Times-Roman", 12.0); /* not scaled */ + fontspec(&f->headerfont, "Times-Roman", 12.0); /* not scaled */ + fontspec(&f->repeatfont, "Times-Roman", 13.0); + fontspec(&f->measurefont, "Times-Italic", 14.0); +} + +/* -- read a boolean value -- */ +static int g_logv(char *l) +{ + switch (*l) { + case 0: + case '1': + case 'y': + case 'Y': + case 't': + case 'T': + return 1; + case '0': + case 'n': + case 'N': + case 'f': + case 'F': + break; + default: + alert("++++ Unknown logical '%s' - false assumed", l); + break; + } + return 0; +} + +/* -- read a float variable, no units -- */ +static float g_fltv(char *l) +{ + return atof(l); +} + +/* -- read a font specifier -- */ +static void g_fspc(char *p, + struct FONTSPEC *fn) +{ + char fname[STRLFMT]; + float fsize; + + fsize = fn->size; + p = get_str(fname, p, sizeof fname); + if (*p != '\0') + fsize = g_fltv(p); + fontspec(fn, + strcmp(fname, "*") != 0 ? fname : 0, + fsize); + if (!file_initialized) + used_font[fn->fnum] = 1; +} + +/* -- parse a format line -- */ +/* return: + * 0: format modified + * 1: 'end' found + * 2: format not modified */ +int interpret_format_line(char *w, /* keyword */ + char *p) /* argument */ +{ + struct format *fd; + if (*w == '\0' + || *w == '%') + return 2; + if (strcmp(w, "end") == 0) + return 1; + + if (strcmp(w, "deco") == 0) { + deco_add(p); + return 2; + } + + if (strcmp(w, "postscript") == 0) { + if (!file_initialized) + add_text(p, TEXT_PS); + return 2; + } + + for (fd = format_tb; fd->name; fd++) + if (strcmp(w, fd->name) == 0) + break; + if (fd->name) { + switch (fd->type) { + case FORMAT_I: + sscanf(p, "%d", (int *) fd->v); + switch (fd->subtype) { + case 1: + if ((unsigned) cfmt.encoding > MAXENC) { + alert("Bad encoding value %d - reset to 0", + cfmt.encoding); + cfmt.encoding = 0; + } + break; + case 2: + nbar = nbar_rep = cfmt.measurefirst; + break; + } + break; + case FORMAT_R: + *((float *) fd->v) = g_fltv(p); + if (fd->subtype == 1) { /* note spacing factor */ + int i; + float wid; + + if (cfmt.notespacingfactor <= 0) { + alert("Bad value for 'notespacingfactor'"); + break; + } + dot_space = sqrt(cfmt.notespacingfactor); + wid = space_tb[SPACETB_SZ/2]; + for (i = SPACETB_SZ/2; --i >= 0; ) { + wid /= cfmt.notespacingfactor; + space_tb[i] = wid; + } + wid = space_tb[SPACETB_SZ/2]; + for (i = SPACETB_SZ/2; ++i < SPACETB_SZ; ) { + wid *= cfmt.notespacingfactor; + space_tb[i] = wid; + } + } + break; + case FORMAT_F: { + int b; + + g_fspc(p, (struct FONTSPEC *) fd->v); + b = strstr(p, "box") != 0; + switch (fd->subtype) { + case 1: + cfmt.partsbox = b; + break; + case 2: + cfmt.measurebox = b; + break; + case 3: + cfmt.gchordbox = b; + break; + } + break; + } + case FORMAT_U: + *((float *) fd->v) = scan_u(p); + if (fd->subtype == 1) { + float rmargin; + + rmargin = (cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - staffwidth - cfmt.leftmargin; + if (rmargin < 0) + alert("'staffwidth' too big"); + cfmt.rightmargin = rmargin; + } + break; + case FORMAT_B: + *((int *) fd->v) = g_logv(p); + break; + case FORMAT_S: { + int l; + + l = strlen(p) + 1; + *((char **) fd->v) = (char*)malloc(l); + if (*p == '"') + get_str(*((char **) fd->v), p, l); + else strcpy(*((char **) fd->v), p); + break; + } + } + return 0; + } + else + alert("format '%s'?",w); + + if (!strcmp(w, "font")) { + int fnum; + + fnum = add_font(p); + used_font[fnum] = 1; + return 0; + } + return 2; +} + +/* -- read a format file -- */ +int read_fmt_file(const char *filename, + const char *dirname) +{ + FILE *fp; + char fname[256]; + + strcpy(fname, filename); + if ((fp = fopen(fname, "r")) == 0) { + if (*dirname == 0) + return -1; + sprintf(fname, "%s%c%s", dirname, DIRSEP, filename); + if ((fp = fopen(fname, "r")) == 0) + return -1; + } + for (;;) { + char line[BSIZE], *p, *q; + + if (!fgets(line, sizeof line, fp)) + break; + line[strlen(line) - 1] = '\0'; /* remove '\n' */ + q = line; + while (isspace(*q)) + q++; + p = q; + while (*p != '\0' && !isspace(*p)) + p++; + if (*p != '\0') + *p++ = '\0'; + while (isspace(*p)) + p++; + if (interpret_format_line(q, p) == 1) + break; + } + fclose(fp); + return 0; +} + +/* -- start a new font -- */ +void set_font(struct FONTSPEC *font) +{ + int fnum; + + fnum = font->fnum; + if (!used_font[fnum]) { + alert("Font \"%s\" not predefined; using first in list",fontnames[fnum]); + fnum = 0; + } + PUT2("%.1f F%d ", font->size, fnum); +} diff --git a/src-abcm2ps/landscape.fmt b/src-abcm2ps/landscape.fmt new file mode 100644 index 0000000..d0eeaff --- /dev/null +++ b/src-abcm2ps/landscape.fmt @@ -0,0 +1,9 @@ + +% format for wide output in landscape mode + + landscape 1 + + titleleft yes + titlefont Palatino-Bold 22 + composerspace 0.6cm + staffsep 60 diff --git a/src-abcm2ps/music.cpp b/src-abcm2ps/music.cpp new file mode 100644 index 0000000..07a41f4 --- /dev/null +++ b/src-abcm2ps/music.cpp @@ -0,0 +1,3019 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +float realwidth; /* real staff width while generating */ + +static int insert_meter; /* flag to insert time signature */ + +static float alfa_last, beta_last; /* for last short short line.. */ + +static struct SYMBOL *tssym; /* time sorted list of symbols */ +static struct SYMBOL *tsnext; /* next line when cut */ + +#define AT_LEAST(a,b) do { float tmp = b; if(aun.note.grace) { + dx = 9.0; + switch (s->head) { + case H_SQUARE: + case H_OVAL: + dx = 12.0; + break; + } + } else dx = 6.0; + n = s->un.note.nhd; + for (i = 0; i <= n; i++) + s->shac[i] = dx; + if (n == 0) + return; + + dx = 7.0; + switch (s->head) { + case H_SQUARE: + dx = 13.0; + break; + case H_OVAL: + dx = 10.0; + break; + case H_EMPTY: + dx = 7.8; + break; + } + i1 = 1; + i2 = n + 1; + sig = s->stem; + if (sig < 0) { + dx = -dx; + i1 = n - 1; + i2 = -1; + } + shift = 0; /* shift heads */ + for (i = i1; i != i2; i += sig) { + d = s->pits[i] - s->pits[i - sig]; + if (d < 0) + d = -d; + if (d > 3 || (d >= 2 && s->head < H_SQUARE)) + shift = 0; + else { + shift = !shift; + if (shift) { + s->shhd[i] = dx; + if (dx > s->xmx) + s->xmx = dx; + } + } + } + + shift = 0; /* shift accidentals */ + for (i = n; i >= 0; i--) { + xmn = 0; /* left-most pos of a close head */ + nac = 99; /* relative pos of next acc above */ + for (m = 0; m <= n; m++) { + float xx; + int da; + + xx = s->shhd[m]; + d = s->pits[m] - s->pits[i]; + da = d > 0 ? d : -d; + if (da <= 5 && xx < xmn) + xmn = xx; + if (d > 0 && da < nac && s->un.note.accs[m]) + nac = da; + } + s->shac[i] = 9. - xmn + s->shhd[i]; /* aligns accidentals in column */ + switch (s->head) { + case H_SQUARE: + s->shac[i] += 5.; + break; + case H_OVAL: + s->shac[i] += 3.; + break; + case H_EMPTY: + s->shac[i] += 1.; + break; + } + if (s->un.note.accs[i]) { + if (nac >= 6) { /* no overlap */ + shift = 0; + continue; + } + if (nac >= 4) { /* weak overlap */ + if (shift != 0) { + shift = 0; + continue; + } + shift = 1; + } else { /* strong overlap */ + if (shift > 2) { + shift = 0; + continue; + } + shift += 2; + } + if (shift == 1) + s->shac[i] += 4.5; + else s->shac[i] += 3.5 * shift; + } + } + + /* adjust for grace notes */ + if (s->un.note.grace) { + for (i = n; i >= 0; i--) { + s->shhd[i] *= 0.7; + s->shac[i] *= 0.7; + } + } +} + +/* -- insert a clef change (treble or bass) -- */ +static void insert_clef(struct SYMBOL *s, + int clef_type) +{ + struct SYMBOL *new_s; + int time, seq; + + /* create the symbol */ + new_s = ins_sym(CLEF, s->prv); + new_s->un.clef.type = clef_type; + new_s->un.clef.line = clef_type == TREBLE ? 2 : 4; + new_s->u = 1; /* small clef */ + + /* link in time */ + time = s->time; + seq = s->seq; + do { + s = s->ts_prev; + } while (s->time == time + && s->seq == seq); + new_s->ts_next = s->ts_next; + new_s->ts_next->ts_prev = new_s; + s->ts_next = new_s; + new_s->ts_prev = s; + new_s->time = time; +} + +/* -- define the clef for a staff -- */ +/* this function is called only once for the whole tune */ +static void set_clef(int staff) +{ + struct SYMBOL *s; + int min, max; + struct SYMBOL *last_chg; + int clef_type; + + min = max = 16; /* 'C' */ + + /* count the number of notes upper and lower than 'C' */ + for (s = tssym; s != 0; s = s->ts_next) { + int xp; + + if (s->staff != staff + || s->type != NOTE) + continue; + xp = s->nhd; + if (s->pits[xp] > max) + max = s->pits[xp]; + else if (s->pits[0] < min) + min = s->pits[0]; + } + + staff_tb[staff].clef.type = TREBLE; + staff_tb[staff].clef.line = 2; + if (min >= 13) /* all upper than 'G,' --> treble clef */ + return; + if (max <= 19) { /* all lower than 'F' --> bass clef */ + staff_tb[staff].clef.type = BASS; + staff_tb[staff].clef.line = 4; + return; + } + + /* set clef changes */ + clef_type = TREBLE; + last_chg = 0; + for (s = tssym; s != 0; s = s->ts_next) { + struct SYMBOL *s2, *s3; + + if (s->staff != staff + || s->type != NOTE) + continue; + + /* check if a clef change must occur */ + if (clef_type == TREBLE) { + if (s->pits[0] > 12 /* F, */ + || s->pits[s->nhd] > 20) /* G */ + continue; + s2 = s->ts_prev; + if (s2->time == s->time + && s2->staff == staff + && s2->pits[0] >= 19) /* F */ + continue; + s2 = s->ts_next; + if (s2 != 0 + && s2->staff == staff + && s2->time == s->time + && s2->pits[0] >= 19) /* F */ + continue; + } else { + if (s->pits[0] < 12 /* F, */ + || s->pits[s->nhd] < 20) /* G */ + continue; + s2 = s->ts_prev; + if (s2->time == s->time + && s2->staff == staff + && s2->pits[0] <= 13) /* G, */ + continue; + s2 = s->ts_next; + if (s2 != 0 + && s2->staff == staff + && s2->time == s->time + && s2->pits[0] <= 13) /* G, */ + continue; + } + + /* go backwards and search where to insert a clef change */ + if (!voice_tb[s->voice].second) + s3 = s; + else s3 = 0; + for (s2 = s->ts_prev; s2 != last_chg; s2 = s2->ts_prev) { + if (s2->staff != staff) + continue; + if (s2->type == BAR) { + if (voice_tb[s2->voice].second) + continue; + s3 = s2; + break; + } + if (s2->len == 0) /* neither note nor rest */ + continue; + + /* exit loop if a clef change cannot occur */ + if (s2->type == NOTE) { + if (clef_type == TREBLE) { + if (s2->pits[0] >= 19) /* F */ + break; + } else { + if (s2->pits[s2->nhd] <= 13) /* G, */ + break; + } + } + + /* have a 2nd choice if word starts on the main voice */ + if (!voice_tb[s2->voice].second) { + if (s2->sflags & S_WORD_ST + || s3 == 0 + || (s3->sflags & S_WORD_ST) == 0) + s3 = s2; + } + } + s2 = last_chg; + last_chg = s; + + /* if first change, see if any note before */ + if (s2 == 0) { + struct SYMBOL *s4; + + for (s4 = s->ts_prev; s4 != 0; s4 = s4->ts_prev) { + if (s4->staff != staff) + continue; + if (s4->type == NOTE) + break; + } + + /* if no note, change the clef of the staff */ + if (s4 == 0) { + if (clef_type == TREBLE) { + clef_type = BASS; + staff_tb[staff].clef.line = 4; + } else { + clef_type = TREBLE; + staff_tb[staff].clef.line = 2; + } + staff_tb[staff].clef.type = clef_type; + continue; + } + } + + /* no change possible if no insert point */ + if (s3 == 0 || s3 == s2) + continue; + + /* insert a clef change */ + clef_type = clef_type == TREBLE ? BASS : TREBLE; + insert_clef(s3, clef_type); + } +} + +/* -- sort the symbols by time -- */ +/* this function is called only once for the whole tune */ +static void def_tssym(void) +{ + struct SYMBOL *s, *t, *prev_sym; + int time, bars; + struct VOICE_S *p_voice; + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + p_voice->s_anc = 0; + p_voice->selected = 0; + } + + /* sort the symbol by time */ + prev_sym = 0; + tssym = 0; + s = 0; /* compiler warning */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if ((s = p_voice->sym) == 0) { + alert( "Voice %s is empty", p_voice->name); + continue; + } + s->ts_prev = prev_sym; + if (prev_sym != 0) + prev_sym->ts_next = s; + else tssym = s; + prev_sym = s; + p_voice->s_anc = s->nxt; + } + bars = 0; /* (for errors) */ + for (;;) { + int seq; + + /* search the closest next time/sequence */ + time = (unsigned) ~0 >> 1; /* max int */ + seq = -1; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if ((s = p_voice->s_anc) == 0 + || s->time > time) + continue; + if (s->time < time + || s->seq < seq) { + time = s->time; + seq = s->seq; + } + } + if (seq < 0) + break; /* echu (finished) */ + + /* warn about incorrect number of notes / measures */ + t = 0; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if ((s = p_voice->s_anc) != 0 + && s->time == time + && s->seq == seq) { + p_voice->selected = 1; + if (s->type == BAR + && s->un.bar.type != B_INVIS + && t == 0) + t = s; + } else p_voice->selected = 0; + } + + if (t != 0) { + int ko = 0; + + bars++; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if ((s = p_voice->s_anc) == 0) + continue; + if (s->len > 0) { /* note or rest */ + ko = 1; + break; + } + else if (s->time != time) { /* misplaced bar */ + ko = 2; + break; + } + p_voice->selected = 1; + } + if (ko) { + int newtime; + if (ko==1) + alert("Too many notes in measure %d for voice %s (line %d)", + bars, p_voice->name, t->linenum); + else if (ko==2) + alert("Misplaced bar in measure %d (near voice %s) (line %d)", + bars, p_voice->name,t->linenum); + for (p_voice = first_voice; + p_voice; + p_voice = p_voice->next) { + if ((t = p_voice->s_anc) == 0 + || t->type != BAR) + continue; + newtime = s->time + s->len; + for (; t != 0; t = t->nxt) { + t->time = newtime; + newtime += t->len; + } + } + bars--; + continue; + } + } + + /* set the time linkage */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if (!p_voice->selected) + continue; + s = p_voice->s_anc; + s->ts_prev = prev_sym; + prev_sym->ts_next = s; + prev_sym = s; + p_voice->s_anc = s->nxt; + } + } +} + +/* -- set the staff of the floating voices -- */ +/* this function is called only once per tune */ +static void set_float(void) +{ + struct VOICE_S *p_voice; + int staff, staff_chg; + struct SYMBOL *s, *s1; + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if (!p_voice->floating) + continue; + staff_chg = 0; + staff = p_voice->staff; + for (s = p_voice->sym; s != 0; s = s->nxt) { + int up, down; + + if (s->type != NOTE) { + if (staff_chg) + s->staff++; + continue; + } + if (s->pits[0] >= 19) { /* F */ + staff_chg = 0; + continue; + } + if (s->pits[s->nhd] <= 13) { /* G, */ + staff_chg = 1; + s->staff++; + continue; + } + up = 127; + for (s1 = s->ts_prev; s1 != 0; s1 = s1->ts_prev) { + if (s1->staff != staff + || s1->voice == s->voice) + break; + if (s1->type == NOTE + && s1->pits[0] < up) + up = s1->pits[0]; + } + if (up == 127) { + if (staff_chg) + s->staff++; + continue; + } + if (s->pits[s->nhd] > up - 3) { + staff_chg = 0; + continue; + } + down = -127; + for (s1 = s->ts_next; s1 != 0; s1 = s1->ts_next) { + if (s1->staff != staff + 1 + || s1->voice == s->voice) + break; + if (s1->type == NOTE + && s1->pits[s1->nhd] > down) + down = s1->pits[s1->nhd]; + } + if (down == -127) { + if (staff_chg) + s->staff++; + continue; + } + if (s->pits[0] < down + 3) { + staff_chg = 1; + s->staff++; + continue; + } + up -= s->pits[s->nhd]; + down = s->pits[0] - down; + if (!staff_chg) { + if (up < down + 3) + continue; + staff_chg = 1; + } else { + if (up < down - 3) { + staff_chg = 0; + continue; + } + } + s->staff++; + } + } +} + +/* -- set the pitch of the notes according to the clefs -- */ +/* also set the vertical offset of notes and rests */ +/* it supposes that the first symbol of each voice is the clef */ +/* this function is called only once per tune */ +static void set_pitch(void) +{ + struct SYMBOL *s; + int staff; + char staff_clef[MAXSTAFF]; + + for (s = tssym; s != 0; s = s->ts_next) { + struct SYMBOL *g; + int delta; + int np, m, pav; + + staff = s->staff; + switch (s->type) { + case CLEF: + if (!voice_tb[s->voice].second) { + delta = 0 - 2 * 2; /* treble value */ + switch (s->un.clef.type) { + case ALTO: delta = 6 - 3 * 2; break; + case BASS: delta = 12 - 4 * 2; break; + } + staff_clef[staff] = delta + + s->un.clef.line * 2; + } + /* fall thru */ + default: { + int pmn, pmx; + + s->ymn = -6; + s->ymx = 24 + 6; + s->dc_top = 24. + 2.; + s->dc_bot = -2.; + if ((g = s->grace) == 0) + continue; + delta = staff_clef[staff]; + if (delta != 0) { + for (; g != 0; g = g->nxt) { + for (m = g->nhd; m >= 0; m--) + g->pits[m] += delta; + } + g = s->grace; + } + pmn = 0; + pmx = 24; + for (; g != 0; g = g->nxt) { + g->ymn = g->y = 3 * (g->pits[0] - 18); + g->ymx = 3 * (g->pits[g->nhd] - 18); + g->ys = g->ymx + GSTEM; + if (g->nflags > 0) + g->ys += 1.2 * (g->nflags - 1); + if (g->ymn < pmn) + pmn = g->ymn; + else if (g->ys > pmx) + pmx = (int)g->ys; + } + s->dc_top = pmx + 2; + s->dc_bot = pmn - 2; + continue; + } + case MREST: + s->ymn = -6; + s->ymx = 24 + 18; + s->dc_top = 24 + 15; + s->dc_bot = -2; + continue; + case REST: + s->y = 12; + s->ymn = 0; + s->ymx = 24; + s->dc_top = 12. + 8.; + s->dc_bot = 12. - 8.; + continue; + case NOTE: + break; + } + np = s->nhd; + delta = staff_clef[staff]; + if (delta != 0) { + for (m = np; m >= 0; m--) + s->pits[m] += delta; + } + pav = 0; + for (m = np; m >= 0; m--) + pav += s->pits[m]; + s->y = 3 * (s->pits[0] - 18); + s->ymn = s->y; + s->ymx = 3 * (s->pits[np] - 18); + s->yav = 3 * ((pav / (np + 1)) - 18); + s->dc_top = (float) (s->ymx + 2); + s->dc_bot = (float) (s->ymn - 2); + } +} + +/* -- set the stem direction when multi-voices -- */ +/* and adjust the vertical offset of the rests */ +/* this function is called only once per tune */ +static void set_multi(void) +{ + struct SYMBOL *s; + int i, staff, us, ls; + struct { + short nvoice; + short last; + struct { + short voice; + short nn; + short ymn; + short ymx; + } st[4]; /* (no more than 4 voices per staff) */ + } stb[MAXSTAFF]; + struct VOICE_S *p_voice; + + memset(stb, 0, sizeof stb); + for (staff = MAXSTAFF; --staff >= 0; ) { + for (i = 4; --i >= 0; ) + stb[staff].st[i].voice = -1; + } + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + staff = p_voice->staff; + for (i = 0; i < 4; i++) { + if (stb[staff].st[i].voice < 0) { + stb[staff].st[i].voice = p_voice - voice_tb; + break; + } + } + if (p_voice->floating) { + staff++; + for (i = 0; i < 4; i++) { + if (stb[staff].st[i].voice < 0) { + stb[staff].st[i].voice = p_voice - voice_tb; + break; + } + } + } + } + s = tssym; + while (s != 0) { + struct SYMBOL *t; + + for (staff = MAXSTAFF; --staff >= 0; ) { + stb[staff].nvoice = 0; + stb[staff].last = 0; + for (i = 4; --i >= 0; ) { + stb[staff].st[i].nn = 0; + stb[staff].st[i].ymx = 0; + stb[staff].st[i].ymn = 24; + } + } + + /* go to the next bar and get the max/min offsets */ + for (t = s; + t != 0 && t->type != BAR; + t = t->ts_next) { + if (t->len == 0 /* not a note or a rest */ + || t->un.note.invis) + continue; + staff = t->staff; + for (i = 0; i < 4; i++) { + if (stb[staff].st[i].voice == t->voice) + break; + } + if (i == 4) + bug("Voice with no staff", 1); + + if (++stb[staff].st[i].nn == 1) + stb[staff].nvoice++; + if (i > stb[staff].last) + stb[staff].last = i; + if (t->type != NOTE) + continue; + if (t->dc_top > stb[staff].st[i].ymx) + stb[staff].st[i].ymx = (int)t->dc_top; + if (t->dc_bot < stb[staff].st[i].ymn) + stb[staff].st[i].ymn = (int)t->dc_bot; + } + + for ( ; + s != 0 && s->type != BAR; + s = s->ts_next) { + int j; + + staff = s->staff; + if (stb[staff].nvoice <= 1) { + + /* only 1 voice in the staff */ + p_voice = &voice_tb[s->voice]; + if (p_voice->floating) { + if (s->staff == p_voice->staff) + s->multi = -1; + else s->multi = 1; + } + continue; + } + for (i = 0; i < 4; i++) { + if (stb[staff].st[i].voice == s->voice) + break; + } + if (i == stb[staff].last) + s->multi = -1; /* last voice */ + else { + s->multi = 1; /* first voice(s) */ + + /* if 3 voices, and vertical space enough, + * have stems down for the middle voice */ + if (i != 0 + && i + 1 == stb[staff].last) { + if (stb[staff].st[i].ymn - STEM + > stb[staff].st[i + 1].ymx) + s->multi = -1; + + /* special case for unisson */ + t = s->ts_next; + if (s->pits[s->nhd] == s->ts_prev->pits[0] + && (s->sflags & S_WORD_ST) + && s->un.note.word_end + && (t == 0 + || t->staff != s->staff + || t->time != s->time)) + s->multi = -1; + } + } + if (s->type != REST) + continue; + + /* set the rest vertical offset */ + /* (if visible and invisible rests on the same staff, + * set as if 1 rest only) */ + us = rest_sp[4 - s->nflags].u; + ls = rest_sp[4 - s->nflags].l; + + if (i == 0) { + t = s->ts_next; + if (t != 0 + && t->type == REST + && t->un.note.invis + && t->ts_next != 0 + && t->ts_next->staff == staff + && t->ts_next->time == s->time) + t = t->ts_next; + if (t == 0 + || t->type != REST + || !t->un.note.invis) { + j = i + 1; + if (stb[staff].st[j].nn == 0) + j++; + s->y = (stb[staff].st[j].ymx + ls) + / 6 * 6; + if (s->y < 12) + s->y = 12; + } + } else if (i == stb[staff].last) { + t = s->ts_prev; + if (t->type == REST + && t->un.note.invis + && t->ts_prev != 0 + && t->ts_prev->staff == staff + && t->ts_prev->time == s->time) + t = t->ts_prev; + if (t->type != REST + || !t->un.note.invis) { + j = i - 1; + if (stb[staff].st[j].nn == 0) + j--; + s->y = (stb[staff].st[j].ymn - us + 48) + / 6 * 6 - 48; + if (s->y > 12) + s->y = 12; + } + } else { + + /* rest in the middle of 3 voices */ + s->shhd[0] = 10; + s->wr += 10; +/*fixme: may be too high*/ + s->y = (stb[staff].st[i - 1].ymn + + stb[staff].st[i + 1].ymx) + / 12 * 6; + } + s->dc_top = s->y + us; + if (s->dc_top > stb[staff].st[i].ymx) + stb[staff].st[i].ymx = (int)s->dc_top; + s->dc_bot = s->y - ls; + if (s->dc_bot < stb[staff].st[i].ymn) + stb[staff].st[i].ymn = (int)s->dc_bot; + } + + while (s != 0 && s->type == BAR) + s = s->ts_next; + } +} + +/* -- set the staves and stems when multivoice -- */ +/* this function is called only once per tune */ +static void set_global(void) +{ + int staff; + struct SYMBOL *s; + struct VOICE_S *p_voice; + + int done; + + done = 0; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + int max, min; + + if (!p_voice->forced_clef + || p_voice->clef.type == PERC) + continue; + + /* search if any pitch is too high for the clef */ + max = 100; + min = -100; + for (s = p_voice->sym; s != 0; s = s->nxt) { + switch (s->type) { + case CLEF: + if (s->un.clef.check_pitch == 0) { + max = 100; + min = -100; + continue; + } + switch (s->un.clef.type) { + case TREBLE: + case PERC: + max = 100; + min = -100; + break; + case ALTO: + max = 25; /* e */ + min = 14; /* G, */ + break; + case BASS: + max = 21; /* A */ + min = 10; /* C, */ + break; + } + default: + continue; + case NOTE: + if (s->pits[0] < min) { + done = 1; + break; /* new behaviour */ + } + if (s->pits[s->nhd] <= max) + continue; + done = 1; + break; + } + break; + } + if (done) + break; + } + + /* set the width of the notes/rests, a pitch for all symbols, */ + /* the tie indexes, the start/end of words and the sequence number */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + int pitch, start_flag, seq; + struct SYMBOL *sym, *last_tie, *lastnote; + + sym = p_voice->sym; + + pitch = 22; /* 'B' - if no note! */ + for (s = sym; s != 0; s = s->nxt) { + if (s->type == NOTE) { + pitch = s->pits[0]; + break; + } + } + last_tie = 0; + start_flag = 1; + lastnote = 0; + seq = 0; + for (s = sym; s != 0; s = s->nxt) { + switch (s->type) { + default: + if ((s->sflags & S_EOLN) == 0) + break; + /* fall thru */ + case BAR: + case MREST: + case MREP: + if (lastnote != 0) { + lastnote->un.note.word_end = 1; + start_flag = 1; + lastnote = 0; + } + if (s->type == BAR + && s->nxt == 0 + && s->prv->type == NOTE + && s->prv->un.note.len >= BREVE) + s->prv->head = H_SQUARE; + break; + case NOTE: { + int i; + + if (last_tie != 0) { + for (i = 0; i <= last_tie->nhd; i++) { + int pit, j; + + if (last_tie->un.note.ti1[i] == 0) + continue; + pit = last_tie->pits[i]; + last_tie->un.note.ti1[i] = 0; + for (j = 0; j <= s->un.note.nhd; j++) { + if (s->pits[j] == pit) { + last_tie->un.note.ti1[i] = j + 1; + break; + } + } + } + last_tie = 0; + } + for (i = 0; i <= s->nhd; i++) { + if (s->un.note.ti1[i] != 0) { + last_tie = s; + break; + } + } + } + /* fall thru */ + case REST: + + /* set the note widths */ + switch (s->head) { + case H_SQUARE: + s->wl = s->wr = 8.0; + break; + case H_OVAL: + s->wl = s->wr = 6.0; + break; + case H_EMPTY: + s->wl = s->wr = 5.0; + break; + default: + s->wl = s->wr = 4.5; + break; + } + + if (s->nflags <= 0) { + if (lastnote != 0) { + lastnote->un.note.word_end = 1; + lastnote = 0; + } + s->un.note.word_end = start_flag = 1; + s->sflags |= S_WORD_ST; + } else if (s->type == NOTE) { + if (start_flag) + s->sflags |= S_WORD_ST; + if (s->sflags & S_EOLN) { + s->un.note.word_end = 1; + start_flag = 1; + } + start_flag = s->un.note.word_end; + lastnote = s; + } else if (s->un.note.word_end) { + if (lastnote != 0) { + lastnote->un.note.word_end = 1; + lastnote = 0; + } + s->un.note.word_end = 0; + start_flag = 1; + } + break; + } + if (s->type == NOTE) { + pitch = s->pits[0]; + if (s->prv->type != NOTE) { + s->prv->pits[0] = (s->prv->pits[0] + + pitch) / 2; + } + } else s->pits[0] = pitch; + switch (s->type) { + case NOTE: + case MREST: + case MREP: + case FMTCHG: + seq = 0; + break; + case REST: + if (s->len != 0) { + seq = 0; + break; + } + s->seq = 0; /* space ('y') */ + default: + if (s->seq <= seq) + s->seq = seq + 1; + seq = s->seq; + break; + } + } + if (lastnote != 0) + lastnote->un.note.word_end = 1; + } + + /* sort the symbols by time */ + def_tssym(); + + /* set the staff of the floating voices */ + set_float(); + + /* set the clefs */ + if (cfmt.autoclef) { + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + if (p_voice->forced_clef) + staff_tb[p_voice->staff].forced_clef = 1; + for (staff = 0; staff <= nstaff; staff++) { + if (!staff_tb[staff].forced_clef) + set_clef(staff); + } + } + + /* set the starting clefs and adjust the note pitches */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + memcpy(&p_voice->sym->un.clef, + &staff_tb[p_voice->staff].clef, + sizeof p_voice->sym->un.clef); + set_pitch(); +} + +/* -- return the left indentation of the staves -- */ +static float set_indent(int first_line) +{ + int staff; + float more_shift, w, maxw; + struct VOICE_S *p_voice; + char t[64], *p, *q; + + maxw = 0; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + p = first_line ? p_voice->nm : p_voice->snm; + if (p == 0) + continue; + for (;;) { + if ((q = strstr(p, "\\n")) != 0) + *q = '\0'; + tex_str(t, p, sizeof t, &w); + if (w > maxw) + maxw = w; + if (q == 0) + break; + *q = '\\'; + p = q + 2; + } + } + + /* if no name, indent the first line when many lines */ + if (maxw == 0) + return first_line + ? (tsnext == 0 ? 0 : cfmt.indent) + : 0; + + more_shift = 0; + for (staff = 0; staff <= nstaff; staff++) { + if (staff_tb[staff].brace + || staff_tb[staff].bracket) { + more_shift = 10; + break; + } + } + return cfmt.vocalfont.swfac * cfmt.vocalfont.size * (maxw + 4 * cwid(' ')) + + more_shift; +} + +/* -- set the y offset of the staves and return the whole height -- */ +/* !! this routine is tied to draw_vocals() !! */ +static float set_staff(void) +{ + struct SYMBOL *s; + struct VOICE_S *p_voice; + int staff; + float y, vocal_height; + float staffsep, dy; + struct { + char avoc, bvoc; /* number of vocals above and below the staff */ + short vocal; /* some vocal below the staff */ + float x; + float ctop, mtop; + float cbot, mbot; + } delta_tb[MAXSTAFF], *p_delta; + int any_part, any_tempo; + + memset(delta_tb, 0, sizeof delta_tb); + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + if (p_voice->nvocal > 0) { + staff = p_voice->staff; + if (cfmt.vocalabove + || (p_voice->next != 0 + && p_voice->next->staff == staff + && p_voice->next->nvocal > 0)) { + delta_tb[staff].avoc += p_voice->nvocal; + if (staff > 0) + delta_tb[staff - 1].vocal = 1; + } else { + delta_tb[staff].bvoc += p_voice->nvocal; + delta_tb[staff].vocal = 1; + } + } + } + delta_tb[nstaff].vocal = 1; + + any_part = any_tempo = 0; + for (s = tssym; s != 0; s = s->ts_next) { +/*fixme: bad! better have a flag in the voice*/ + if (s->voice == first_voice - voice_tb) { + switch (s->type) { + case PART: + if (cfmt.printparts) + any_part = 1; + break; + case TEMPO: + any_tempo = 1; + break; + } + } + staff = s->staff; + p_delta = &delta_tb[staff]; + if (s->x > p_delta->x) { + p_delta->x = s->x + 8; + p_delta->ctop = s->dc_top; + p_delta->cbot = s->dc_bot; + } else { + if (s->dc_top > p_delta->ctop) + p_delta->ctop = s->dc_top; + if (s->dc_bot < p_delta->cbot) + p_delta->cbot = s->dc_bot; + } + + /* adjust above the staff */ + if (staff == 0 + || p_delta[-1].vocal) { + if (p_delta->mtop < p_delta->ctop) + p_delta->mtop = p_delta->ctop; + } else { + dy = p_delta[-1].cbot - p_delta->ctop; + if (p_delta[-1].mbot > dy) + p_delta[-1].mbot = dy; + } + + /* adjust below the staff */ + if (p_delta->vocal) { + if (p_delta->mbot > p_delta->cbot) + p_delta->mbot = p_delta->cbot; + } else { + dy = p_delta->cbot - p_delta[1].ctop; + if (p_delta->mbot > dy) + p_delta->mbot = dy; + } + } + + /* draw the parts and tempo indications if any */ + dy = 0; + if (any_part || any_tempo) + dy = draw_partempo(delta_tb[0].mtop, + any_part, + any_tempo, + delta_tb[0].avoc); + + /* set the staff offsets */ + staffsep = cfmt.staffsep * 0.5 + 24.; + y = 0; + vocal_height = 1.1 * cfmt.vocalfont.size; + for (staff = 0, p_delta = delta_tb; + staff <= nstaff; + staff++, p_delta++) { + p_delta->ctop = y + dy; + dy += p_delta->mtop + vocal_height * p_delta->avoc + 2.; + if (dy < staffsep) + dy = staffsep; + y += dy; + staff_tb[staff].y = -y; + if (staff == 0) + staffsep = cfmt.sysstaffsep + 24.; + dy = -p_delta->mbot; + p_delta->cbot = y + dy + 2.; + if (p_delta->bvoc > 0) { + dy += cfmt.vocalfont.size; + if (dy < cfmt.vocalspace) + dy = cfmt.vocalspace; + dy += vocal_height * (p_delta->bvoc - 1) + + cfmt.vocalfont.size * 0.4; + } + } + staffsep = cfmt.staffsep * 0.5; + if (p_delta->bvoc > 0) + dy += staffsep * 0.5; + else if (dy < staffsep) + dy = staffsep; + + /* set the vocal offsets */ + for (p_voice= first_voice; p_voice; p_voice = p_voice->next) { + if (p_voice->nvocal > 0) { + staff = p_voice->staff; + p_delta = &delta_tb[staff]; + if (cfmt.vocalabove + || (p_voice->next != 0 + && p_voice->next->staff == staff + && p_voice->next->nvocal > 0)) { + p_voice->yvocal = -p_delta->ctop + - cfmt.vocalfont.size; + p_delta->ctop += vocal_height * p_voice->nvocal; + } else { + p_voice->yvocal = -p_delta->cbot - cfmt.vocalfont.size +/*??*/ + 2.; + p_delta->cbot += vocal_height * p_voice->nvocal; + } + } + } + return y + dy; +} + +/* -- decide on beams and on stem directions -- */ +/* this routine is called only once per tune */ +static void set_beams(struct SYMBOL *sym) +{ + struct SYMBOL *s; + int beam, laststem, stem, lasty; + int do_break; + +#if CUT_NPLETS + /* separate words before and after n-plet */ + { + struct SYMBOL *lastnote; + int num, nplet; + + num = nplet = 0; + lastnote = 0; + for (s = sym; s != 0; s = s->nxt) { + if (s->len == 0) /* not a note or a rest */ + continue; + num++; + if (nplet && num == nplet) { + if (lastnote != 0) + lastnote->un.note.word_end = 1; + s->sflags |= S_WORD_ST; + } + if (s->un.note.p_plet) { + nplet = s->un.note.r_plet; + num = 0; + if (lastnote != 0) + lastnote->un.note.word_end = 1; + s->sflags |= S_WORD_ST; + } + lastnote = s; + } + } +#endif + + /* set stem directions; near middle, use previous direction */ + beam = 0; + stem = 0; /* (compilation warning) */ + laststem = 0; + lasty = 0; + do_break = 0; + for (s = sym; s != 0; s = s->nxt) { + if (s->type != NOTE) { + laststem = 0; + continue; + } + if ((s->stem = s->multi) == 0) { + + /* not set by set_multi() (voice alone on the staff) */ + s->stem = s->yav >= 12 ? -1 : 1; + if (laststem != 0 + && s->yav > 11 + && s->yav < 13) { + int dy; + + dy = s->yav - lasty; + if (dy > -7 && dy < 7) + s->stem = laststem; + } + /* notes in a beam have the same stem direction */ + if ((s->sflags & S_WORD_ST) + && !s->un.note.word_end) { /* start of beam */ + int avg, n; + struct SYMBOL *t; + + avg = s->yav; + n = 1; + for (t = s->nxt; t != 0; t = t->nxt) { + if (t->type == NOTE) { + avg += t->yav; + n++; + } + if (t->un.note.word_end) + break; + } + avg /= n; + stem = 1; + if (avg >= 12) { + stem = -1; + if (avg == 12 + && laststem != 0) + stem = laststem; + } + beam = 1; + } + if (beam) + s->stem = stem; + } else { /* stem set by set_multi */ + if ((s->sflags & S_WORD_ST) + && !s->un.note.word_end) { /* start of beam */ + beam = 1; + stem = s->stem; + } + } + + if (s->un.note.word_end) + beam = 0; + if (voice_tb[s->voice].key.bagpipe) + s->stem = -1; + laststem = (s->len >= MINIM || s->un.note.stemless) ? 0 : s->stem; + lasty = s->yav; + if (s->len <= SEMIQUAVER / 2 + && s->prv->len <= SEMIQUAVER / 2) + do_break = 1; + } + + /* set beam breaks on demi-semi-quaver sequences */ + if (do_break) { + int bartime; + struct SYMBOL *s2; + + bartime = 0; + for (s = sym; s != 0; s = s->nxt) { + if (s->type == BAR) { + bartime = s->time; + continue; + } + if (s->len == 0 + || s->len > SEMIQUAVER / 2) + continue; + for (s2 = s; s2 != 0; s2 = s2->prv) { + if ((s2->time - bartime) % QUAVER == 0) { + do { + s2 = s2->prv; + } while (s2 != 0 && s2->type != NOTE); + if (s2 != 0) + s2->sflags |= S_BEAM_BREAK; + break; + } + } + s2 = s; + if ((s = s->nxt) == 0) + break; + while ((s->time - bartime) % QUAVER != 0) { + if (s->type == NOTE) + s2 = s; + if ((s = s->nxt) == 0) + break; + } + if (s == 0) + break; + s2->sflags |= S_BEAM_BREAK; + } + } +} + +/* -- set the x offset of the grace notes -- */ +static float set_graceoffs(struct SYMBOL *s) +{ + struct SYMBOL *g, *next; + int m; + float xx; + + xx = 0; + g = s->grace; + g->sflags |= S_WORD_ST; + for ( ; ; g = g->nxt) { + set_head_directions(g); + for (m = g->nhd; m >= 0; m--) { + if (g->un.note.accs[m]) { + xx += 3.5; + break; + } + } + g->x = xx; + + if (g->nflags <= 0) { + g->sflags |= S_WORD_ST; + g->un.note.word_end = 1; + } + next = g->nxt; + if (next == 0) { + g->un.note.word_end = 1; + break; + } + if (next->nflags <= 0) + g->un.note.word_end = 1; + if (g->un.note.word_end) { + next->sflags |= S_WORD_ST; + xx += GSPACE / 4; + } + if (g->nflags <= 0) + xx += GSPACE / 4; + if (g->y > next->y + 8) + xx -= 1.6; + xx += GSPACE; + } + + /* return the whole width */ + return xx; +} + +/* -- set a shift when notes overlap -- */ +/* this routine is called only once per tune */ +static void set_overlap(void) +{ + struct SYMBOL *s, *s1; + +/*fixme: the accidentals are not treated.. */ + for (s = tssym; s != 0; s = s->ts_next) { + struct SYMBOL *s2; + int d, m, nhd2; + float d1, d2, x1, x2, dy1, dy2; + float noteshift; + + if (s->type != NOTE) + continue; + + s2 = s->ts_next; + if (s2 == 0 + || s->staff != s2->staff + || s->time != s2->time) + continue; + if (s2->type != NOTE) { + if (s2->type != REST) + continue; + s2 = s2->ts_next; + if (s2 == 0 + || s2->type != NOTE + || s->staff != s2->staff + || s->time != s2->time) + continue; + } + + nhd2 = s2->nhd; + + /* align the accidentals when bigger than SEMIBREVE */ + if (s->head >= H_OVAL) { + if (s2->head < H_OVAL) + for (m = nhd2; m >= 0; m--) + s2->shac[m] += 3.; + } else { + if (s2->head >= H_OVAL) + for (m = s->nhd; m >= 0; m--) + s->shac[m] += 3.; + } + + d1 = d2 = x1 = x2 = dy1 = 0; + d = s->pits[0] - s2->pits[nhd2]; + s1 = s; + + /* shift the accidentals */ +/*fixme: not finished... */ + if ((unsigned) d <= 5 && d != 0 + && s1->un.note.accs[0] + && s2->un.note.accs[nhd2]) { +/*fixme*/ + if ((unsigned) d >= 4) + s2->shac[nhd2] += 4.5; + else s2->shac[nhd2] += 7.0; + } + + /* if voices with a same stem direction, force a shift */ + if (s1->stem == s2->stem) { + if (s1->stem > 0) { + if (s2->ys < s1->y + || s2->y > s1->ys) + continue; + } else { + if (s2->y < s1->ys + || s2->ys > s1->y) + continue; + } + if (d > 0) { + s1 = s2; + s2 = s; + d = -d; + } + } + switch (d) { + case 0: { /* unisson */ + int l1, l2; + + if ((l1 = s1->len) >= SEMIBREVE + || (l2 = s2->len) >= SEMIBREVE) + break; + if (s1->stem == s2->stem) + break; + s2->sflags |= S_NO_HEAD; /* same head */ + s2->un.note.accs[nhd2] = 0; + d2 = s1->shhd[0]; + dy2 = 0; + if (l1 == l2) + goto do_shift; + if (l1 < l2) { + l1 = l2; + l2 = s1->len; + } + if (l1 == l2 * 2 + || l1 == l2 * 4 + || l1 == l2 * 8 + || l1 == l2 * 16 + || l1 == l2 * 3 + || l1 * 2 == l2 * 3 + || l1 * 3 == l2 * 4) { + if (l1 < MINIM) { + if (s2->dots > 0) { + s2->sflags &= ~S_NO_HEAD; + s1->sflags |= S_NO_HEAD; + s2->un.note.accs[nhd2] = + s1->un.note.accs[0]; + s1->un.note.accs[0] = 0; + dy2 = -3; + } + goto do_shift; + } + if (l2 < CROTCHET) { /* (l1 == MINIM) */ + if (s2->len == MINIM) { + s2->sflags &= ~S_NO_HEAD; + s1->sflags |= S_NO_HEAD; + s2->un.note.accs[nhd2] = + s1->un.note.accs[0]; + s1->un.note.accs[0] = 0; + } + goto do_shift; + } + } + s2->sflags &= ~S_NO_HEAD; + break; + } + case 1: + break; + default: + if ((s1->head == H_SQUARE + || s2->head == H_SQUARE) + && (d >= -3 && d <= 3)) + break; + if (d > 0) + continue; + if (d < -1 + && s1->head == H_OVAL + && s2->head == H_OVAL) + continue; + break; + } + if (s1->len >= BREVE + || s2->len >= BREVE) + noteshift = 13; + else if (s1->len >= SEMIBREVE + || s2->len >= SEMIBREVE) + noteshift = 10; + else noteshift = 7.8; +/*fixme: treat the accidentals*/ +/*fixme: treat the chord shifts*/ +/*fixme: treat the previous shifts (3 voices or more)*/ + /* the dot of the 2nd voice should be lower */ + dy2 = -3; + + /* if the 1st voice is below the 2nd one, + * shift the 1st voice */ + if (d < 0) { + if (d > -7 + && s2->len < CROTCHET + && (s2->sflags & S_WORD_ST) + && s2->un.note.word_end) + d1 = 8; /* have space for the flag */ +/*fixme: check if overlap in chord*/ + else if (d == -1 || nhd2 != 0 || s->nhd != 0) + d1 = noteshift; + else d1 = noteshift * 0.5; + + /* and shift the dot of the 2nd voice if any */ + if (s2->dots > 0) + x2 = d1; + + /* the dot of the 1st voice must be lower */ + dy1 = -3; + dy2 = 0; + + /* if the upper note is dotted but not the lower one, + * shift the 1st voice */ + } else if (s1->dots > 0) { + if (s2->dots == 0) + d1 = noteshift; + + /* both notes are dotted, shift the 2nd voice */ + else { + d2 = noteshift; + x1 = d2; + } + + /* if the upper note is MINIM or higher, shift the 1st voice */ + } else if (s1->head != H_FULL + && s1->len > s2->len) + d1 = noteshift; + + /* else shift the 2nd voice */ + else d2 = noteshift; + + /* do the shift, and update the width */ + do_shift: + if (d1 > 0) { + d1 += s2->shhd[0]; + for (m = s1->nhd; m >= 0; m--) { + s1->shhd[m] += d1; + s1->shac[m] += d1; + } + s1->xmx += d1; + s2->xmx += x2; + s1->wr += d1 + 2; + s2->wr += d1 + 2; + } else /*if (d2 > 0)*/ { + for (m = nhd2; m >= 0; m--) { + s2->shhd[m] += d2; + s2->shac[m] += d2; + } + s1->xmx += x1; + s2->xmx += d2; + s1->wr += d2 + 2; + s2->wr += d2 + 2; + } + s1->doty = (int)dy1; + s2->doty = (int)dy2; + } +} + +/* -- set the stem lengths -- */ +/* this routine is called only once per tune */ +static void set_stems(void) +{ + struct SYMBOL *s; + + for (s = tssym; s != 0; s = s->ts_next) { + float slen, ymin, ymax; + + if (s->type != NOTE) + continue; + + /* shift notes in chords (need stem direction to do this) */ + set_head_directions(s); + + /* set height of stem end, without considering beaming for now */ + slen = STEM; + if (s->nflags >= 2) { + slen += 2; + if (s->nflags == 3) + slen += 3; + else if (s->nflags >= 4) + slen += 8; + } + if (s->un.note.stemless) { + if (s->stem >= 0) { + s->y = s->ymn; + s->ys = s->ymx; + } else { + s->ys = s->ymn; + s->y = s->ymx; + } + ymin = (float) (s->ymn - 4); + ymax = (float) (s->ymx + 4); + } else if (s->stem >= 0) { + if (s->nflags == 2) + slen -= 1; + if (s->pits[s->nhd] > 26 + && (s->nflags <= 0 + || !((s->sflags & S_WORD_ST) + && s->un.note.word_end))) { + slen -= 2; + if (s->pits[s->nhd] > 28) + slen -= 2; + } + s->y = s->ymn; + s->ys = s->ymx + slen; + if (s->ys < 12) + s->ys = 12; + ymin = (float) (s->ymn - 4); + ymax = s->ys + 2; + if (s->un.note.ti1[0] != 0 + || s->un.note.ti2[0] != 0) + ymin -= 3; + } else { + if (s->pits[0] < 18 + && (s->nflags <= 0 + || !((s->sflags & S_WORD_ST) + && s->un.note.word_end))) { + slen -= 2; + if (s->pits[0] < 16) + slen -= 2; + } + s->ys = s->ymn - slen; + if (s->ys > 12) + s->ys = 12; + s->y = s->ymx; + ymin = s->ys - 2.; + ymax = (float) (s->y + 4); + if (s->un.note.ti1[s->nhd] != 0 + || s->un.note.ti2[s->nhd] != 0) + ymax += 3.; + } + + if (s->dc_bot > ymin) + s->dc_bot = ymin; + if (s->dc_top < ymax) + s->dc_top = ymax; + } +} + +/* -- set width and space of a symbol -- */ +static void set_width(struct SYMBOL *s) +{ + int i, m; + struct SYMBOL *s2; + float xx, w, wlnote, wlw; + char t[81]; + + switch (s->type) { + case NOTE: + case REST: + /* (the note widths are set in set_global) */ + wlnote = s->wl; + + /* room for shifted heads and accidental signs */ + for (m = 0; m <= s->nhd; m++) { + xx = s->shhd[m]; + if (xx != 0) { + AT_LEAST(s->wr, xx + 5.); + AT_LEAST(wlnote, -xx + 5.); + } + if (s->un.note.accs[m]) { + xx -= s->shac[m]; + AT_LEAST(wlnote, -xx + 4.5); + } + } + + /* room for the decorations */ + if (s->un.note.dc.n > 0) + wlnote += deco_width(s); + + /* space for flag if stem goes up on standalone note */ + if ((s->sflags & S_WORD_ST) + && s->un.note.word_end + && s->stem > 0 && s->nflags > 0) + AT_LEAST(s->wr, 10. + s->xmx); + + /* leave room for dots */ + if (s->dots > 0) { + AT_LEAST(s->wr, 12. + s->xmx); + if (s->dots >= 2) + s->wr += 3.5; + switch (s->head) { + case H_SQUARE: + case H_OVAL: + s->wr += 2; + break; + case H_EMPTY: + s->wr += 1; + break; + } + + /* special case: standalone with up-stem and flags */ + if (s->nflags > 0 && s->stem > 0 + && (s->sflags & S_WORD_ST) + && s->un.note.word_end + && !(s->y % 6)) + s->wr += DOTSHIFT; + } + + wlw = wlnote; + + /* extra space when up stem - down stem */ + s2 = s->prv; + if (s2->type == NOTE) { + if (s2->stem > 0 && s->stem < 0) + AT_LEAST(wlw, 7); + + /* make sure helper lines don't overlap */ + if ((s->y > 27 && s2->y > 27) + || (s->y < -3 && s2->y < -3)) + AT_LEAST(wlw, 7.5); + } + + /* leave room for guitar chord */ + /* !! this sequence is tied to draw_gchord() !! */ + if (s->text != 0) { + struct SYMBOL *k; + float lspc, rspc; + char *p, *q; + + p = s->text; + lspc = rspc = 0; + for (;;) { + if ((q = strchr(p, '\n')) != 0) + *q = '\0'; + tex_str(t, p, sizeof t, &w); + p = t; + w *= cfmt.gchordfont.size; + switch (*p) { + case '^': /* above */ + case '_': /* below */ + if (*p == '^') + w -= cwid('^') * cfmt.gchordfont.size; + else w -= cwid('_') * cfmt.gchordfont.size; + /* fall thru */ + default: { /* default = above */ + float wl; + + wl = w * GCHPRE; + if (wl > 8) + wl = 8; + if (wl > lspc) + lspc = wl; + w -= wl; + if (w > rspc) + rspc = w; + break; + } + case '<': /* left */ + w -= cwid('<') * cfmt.gchordfont.size; + w += wlnote; + if (w > lspc) + lspc = w; + break; + case '>': /* right */ + w -= cwid('>') * cfmt.gchordfont.size; + w += s->wr; + if (w > rspc) + rspc = w; + break; + case '@': /* absolute */ + break; + } + if (q == 0) + break; + *q = '\n'; + p = q + 1; + } +/*fixme: pb when '<' only*/ + if (s2->text != 0) + AT_LEAST(wlw, lspc); +/*fixme: pb when '>' only*/ + if ((k = s->nxt) != 0 + && k->text != 0 +/*fixme: may have some other symbols with guitar chords?*/ + && (k->len > 0 || k->type == BAR)) + AT_LEAST(s->wr, rspc + cwid(' ')); + } + + /* leave room for vocals under note */ + /* related to draw_vocals() */ +/*fixme: pb when lyrics of 2 voices in the same staff */ + if (s->ly) { + struct lyrics *ly = s->ly; + float swfac; + + swfac = cfmt.vocalfont.size * cfmt.vocalfont.swfac; + for (i = 0; i < MAXLY; i++) { + struct SYMBOL *k; + float shift; + char *p; + + if ((p = ly->w[i]) == 0) + continue; + p++; + tex_str(t, p, sizeof t, &w); + xx = swfac * (w + 2 * cwid(' ')); + if (isdigit((unsigned) t[0])) + shift = LYDIG_SH * swfac * cwid('1'); + else { + shift = xx * VOCPRE; + if (shift > 20.) + shift = 20.; + } + AT_LEAST(wlw, shift); + if ((k = s->nxt) != 0 + && k->len > 0 /* note or rest */ + && (k->ly == 0 + || k->ly->w[i] == 0 + || k->ly->w[i][0] == '\x02' + || k->ly->w[i][0] == '\x03')) + xx -= 10.; /*fixme: which max width?*/ + AT_LEAST(s->wr, xx - shift); + } + } + + if (s->len != 0) { + xx = space_tb[4 - s->nflags]; + if (s->dots) + xx *= dot_space; + s->pr = bnnp * xx; + s->pl = (1. - bnnp) * xx; + } else s->pl = s->pr = 10; /* space ('y') */ + + /* reduce right space when not followed by a note */ + if (s->nxt != 0 + && s->nxt->len == 0) + s->pr *= 0.8; + + /* squeeze notes a bit if big jump in pitch */ + if (s->type == NOTE + && s2->type == NOTE) { + int dy; + float fac; + + dy = s->y - s2->y; + if (dy < 0) + dy =- dy; + fac = 1. - 0.01 * dy; + if (fac < 0.9) + fac = 0.9; + s->pl *= fac; + + /* stretch / shrink when opposite stem directions */ + if (s2->stem > 0 && s->stem < 0) + s->pl *= 1.1; + else if (s2->stem < 0 && s->stem > 0) + s->pl *= 0.9; + } + + /* if preceeded by a grace note sequence, adjust */ + if (s2->type == GRACE) { + s->wl = wlnote - 4.5; + s->pl = 0; + wlw -= s->wl; + if (s2->wl < wlw - s2->wr) + s2->wl = wlw - s2->wr; + } else s->wl = wlw; + break; + case BAR: + { + int bar_type; + + s->wr = 5; + bar_type = s->un.bar.type; + w = 5; + switch (bar_type) { + case B_OBRA: + case (B_OBRA << 4) + B_CBRA: + s->wr = 0; + w = 0; + break; + case (B_BAR << 4) + B_COL: + case (B_COL << 4) + B_BAR: + w += 3 + 3 + 5; + break; + case (B_COL << 4) + B_COL: + w += 5 + 3 + 3 + 3 + 5; + break; + default: + for (;;) { + switch (bar_type & 0x0f) { + case B_OBRA: + case B_CBRA: + w += 3; + break; + case B_COL: + w += 2; + } + bar_type >>= 4; + if (bar_type == 0) + break; + w += 3; + } + break; + } + s->wl = w; + } + s->pl = s->wl * 1.1; +/*fixme: take the length from the tempo */ + s->pr = s->wr * 1.1; + if (s->un.bar.dc.n > 0) + s->wl += deco_width(s); + + /* have room for the repeat numbers / guitar chord */ + if (s->text == 0) + break; + tex_str(t, s->text, sizeof t, &w); + w += cwid(' ') * 1.5; + xx = cfmt.gchordfont.size * cfmt.gchordfont.swfac * w; + if (!s->un.bar.repeat_bar) { + if (s->prv->text != 0) { + float spc; + + spc = xx * GCHPRE; + if (spc > 8.0) + spc = 8.0; + AT_LEAST(s->wl, spc); + s->pl = s->wl; + xx -= spc; + } + } +/*fixme: may have gchord a bit further..*/ + for (s2 = s->nxt; s2 != 0; s2 = s2->nxt) { + switch (s2->type) { + case PART: + case TEMPO: + continue; + case NOTE: + case REST: + case BAR: + if (s2->text != 0) { + AT_LEAST(s->wr, xx); + s->pr = s->wr; + } + break; + default: + break; + } + break; + } + if (s->nxt != 0 + && s->nxt->text != 0 + && s->nxt->len > 0) { /*fixme: what if 2 bars?*/ + AT_LEAST(s->wr, xx); + s->pr = s->wr; + } + break; + case CLEF: + if (!s->un.clef.invis) { + s->wl = s->pl = 12; + s->wr = s->pr = s->u ? 10 : 16; + } else if (!s->u) { + s->wl = 6; + s->wr = 6; + } + break; + case KEYSIG: { + int n1, n2; + int esp = 4; + + if (s->un.key.nacc == 0) { + n1 = s->un.key.sf; /* new key sig */ + n2 = s->u; /* old key */ + if (n1 * n2 > 0) { + if (n1 < 0 /*|| n2 < 0*/) { + n1 = -n1; + n2 = -n2; + } + if (n2 > n1) + n1 = n2; + } else { + n1 -= n2; + if (n1 < 0) + n1 = -n1; + esp += 3; /* see extra space in draw_keysig() */ + } + } else { + int last_acc; + + n1 = s->un.key.nacc; + last_acc = s->un.key.accs[0]; + for (i = 1; i < n1; i++) { + if (s->un.key.accs[i] != last_acc) { + last_acc = s->un.key.accs[i]; + esp += 3; + } + } + } + if (n1 > 0) { + s->pr = s->wr = (float) (5 * n1 + esp); + s->pl = s->wl = 2; + } else if (s->nxt != 0 && s->nxt->type != TIMESIG) + s->pl = s->wl = 2; + break; + } + case TIMESIG: + /* !!tied to draw_timesig()!! */ + w = 3.5; + for (i = 0; i < s->un.meter.nmeter; i++) { + int l; + + if (s->un.meter.meter[i].top[0] == ' ') { + w += 3; + continue; + } + l = strlen(s->un.meter.meter[i].top); + if (s->un.meter.meter[i].bot[0] != '\0') { + int l2; + + l2 = strlen(s->un.meter.meter[i].bot); + if (l2 > l) + l = l2; + } + w += (float) (3.5 * l); + } + s->pl = s->wl = w; + s->pr = s->wr = w + 5; + break; + case MREST: + s->wl = 40 / 2 + 16; + s->wr = 40 / 2 + 16; + s->pl = s->wl + 16; + s->pr = s->wr + 16; + break; + case MREP: + if (s->un.bar.len == 1) { + s->wr = s->wl = 16 / 2 + 8; + s->pr = s->pl = s->wr + 8; + } else { + s2 = s->prv->prv; /* invisible rest (see parse) */ + s->wl = s2->wl; + s->wr = s2->wr; + s->pl = s2->pl; + s->pr = s2->pr; + } + break; + case GRACE: + s->wl = set_graceoffs(s) + GSPACE * 0.8; + if (s->prv != 0) + s->prv->pr -= s->wl - 10.; + w = GSPACE0; + if ((s2 = s->nxt) != 0 + && s2->type == NOTE) { + struct SYMBOL *g; + + g = s->grace; + while (g->nxt != 0) + g = g->nxt; + if (g->y >= s2->ymx) + w -= 1.; /* above, a bit closer */ + else if ((g->sflags & S_WORD_ST) + && g->y < s2->ymn - 7) + w += 2.; /* below with flag, a bit further */ + } + s->wr = w; + break; + case FMTCHG: + if (s->u != STBRK) + break; + s->wl = s->xmx; +/*fixme: if the last symbol, remove it ??*/ + if (s->nxt->type != CLEF) + s->wr = 8; + else { + s->wr = 2; + s->nxt->u = 0; /* big clef */ + } + s->pl = s->wl + 4; + s->pr = s->wr + 4; + break; + case TEMPO: + case PART: /* no space */ + break; + default: + bug("Cannot set width for symbol", 1); + } +} + +/* -- set widths and prefered space -- */ +/* This routine sets the minimal left and right widths wl,wr + * so that successive symbols are still separated when + * no extra glue is put between them. It also sets the prefered + * spacings pl,pr for good output. + * All distances in pt relative to the symbol center. */ +/* this routine is called only once for the whole tune */ +static void set_sym_widths(void) +{ + struct SYMBOL *s; + + for (s = tssym; s != 0; s = s->ts_next) + set_width(s); +} + +/* -- split up unsuitable bars at end of staff -- */ +static void check_bar(struct SYMBOL *s) +{ + struct VOICE_S *p_voice; + int bar_type; + int i; + + p_voice = &voice_tb[s->voice]; + + if (s->type == KEYSIG && s->prv != 0 && s->prv->type == BAR) + s = s->prv; + if (s->type != BAR) + return; + if (s->un.bar.repeat_bar) { + p_voice->bar_start = B_INVIS; + p_voice->bar_text = s->text; + p_voice->bar_repeat = 1; + s->text = 0; + s->un.bar.repeat_bar = 0; + } + bar_type = s->un.bar.type; + if (bar_type == B_COL) /* ':' */ + return; + if ((bar_type & 0x0f) != B_COL) /* if not left repeat bar */ + return; + if (!(s->sflags & S_RRBAR)) { /* 'xx:' (not ':xx:') */ + p_voice->bar_start = bar_type; + if (s->prv != 0 && s->prv->type == BAR) { + s->prv->nxt = 0; + if (s->ts_prev != 0) + s->ts_prev->ts_next = s->ts_next; + if (s->ts_next != 0) + s->ts_next->ts_prev = s->ts_prev; + } else s->un.bar.type = B_BAR; + return; + } + if (bar_type == B_DREP) { /* '::' */ + s->un.bar.type = B_RREP; + p_voice->bar_start = B_LREP; + return; + } + for (i = 0; bar_type != 0; i++) + bar_type >>= 4; + bar_type = s->un.bar.type; + s->un.bar.type = bar_type >> ((i / 2) * 4); + i = ((i + 1) / 2 * 4); + p_voice->bar_start = bar_type & ((1 << i) - 1); +} + +/* -- set the end of a piece of tune -- */ +/* tsnext is the beginning of the next line */ +static void set_piece(struct SYMBOL *s) +{ + struct VOICE_S *p_voice; + + /* if last line, do nothing */ + if ((tsnext = s->ts_next) == 0) + return; + + /* if the key signature changes on the next line, + * put it at the end of the current line */ + if (tsnext->type == KEYSIG) { + for (s = tsnext; s->ts_next != 0; s = s->ts_next) + if (s->ts_next->type != KEYSIG) + break; + if ((tsnext = s->ts_next) == 0) + return; + } + s->ts_next = 0; + + /* set end of voices */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + int voice; + + voice = p_voice - voice_tb; + for (s = tsnext->ts_prev; s != 0; s = s->ts_prev) { + struct SYMBOL *s2; + + if (s->voice != voice) + continue; + + /* set the word end / start */ + for (s2 = s; s2 != 0; s2 = s2->prv) { + if (s2->type == NOTE) { + s2->un.note.word_end = 1; + break; + } + if (s2->type == BAR) + break; + } + for (s2 = s->nxt; s2 != 0; s2 = s2->nxt) { + if (s2->type == NOTE) { + s2->sflags |= S_WORD_ST; + break; + } + if (s2->type == BAR) + break; + } + s->nxt = 0; + check_bar(s); + break; + } + } +} + +/* -- set the basic spacing before symbol -- */ +static void set_spacing(struct SYMBOL *s) +{ + struct SYMBOL *prev; + + prev = s->prv; + s->shrink = prev->wr + s->wl; + s->x = prev->pr + s->pl; + + switch (s->type) { + case NOTE: + case REST: + if (prev->type == GRACE) + break; + if (prev->type == BAR) + s->x *= 1.5; + if ((s->sflags & S_WORD_ST) == 0) /* reduce spacing within a beam */ + s->x *= fnnp; + if (s->sflags & S_NPLET_END) /* reduce spacing in n-plet */ + s->x *= gnnp; + break; + case CLEF: + if (prev->len == 0) + break; + s->shrink = s->wl + 5; + s->x = s->pl + 5; + break; + } +} + +/* -- Set the characteristics of the glue between symbols, then + * position the symbols along the staff. If staff is overfull, + * only does symbols which fit in, returns this number. -- */ +static void set_sym_glue(float width) +{ + float alfa0, beta0; + float alfa, beta; + int voice, time, seq; + float space, shrink, stretch; + float w; + struct SYMBOL *s; + struct SYMBOL *staff_further[MAXSTAFF]; + + voice = 0; + space = shrink = stretch = 0; /* compiler warnings */ + alfa0 = ALFA_X; /* max shrink and stretch */ + beta0 = BETA_X; + if (cfmt.continueall) { + alfa0 = cfmt.maxshrink; + beta0 = BETA_C; + } + + /* set spacing for the first symbols (clefs - one for each voice) */ + s = tssym; + time = s->time; + seq = s->seq; + while (s != 0 + && s->time == time + && s->seq == seq) { + voice = s->voice; + space = s->x + = shrink = s->shrink + = stretch = s->stretch = s->pl; + staff_further[s->staff] = s; + + /* set spacing for the next symbol */ + if (s->nxt == 0) + bug("Only one symbol", 1); + set_spacing(s->nxt); + s = s->ts_next; + } + /* then loop over the symbols */ + for (;;) { + struct SYMBOL *s2, *s3, *s4; + float max_shrink, max_space; + int len; + struct VOICE_S *p_voice; + + /* get the notes at this time, set spacing + * and get the min shrinking */ + seq = s->seq; + len = s->time - time; + time = s->time; + max_shrink = max_space = 0; + s4 = s; + for (s2 = s; s2 != 0; s2 = s2->ts_next) { + if (s2->time != time + || s2->seq != seq) + break; + + /* if the previous symbol length is greater than + the length from the previous elements, adjust */ + if (s2->prv->len > len) { + if (s2->len == 0) { + if (s2->type != REST) /* (if not space) */ + s2->shrink = s2->x = 0; + } else { + s2->x = (s2->x - 5) * len + / s2->prv->len + 5; + s2->shrink = 6; + } + } + s3 = staff_further[s2->staff]; + if (s2->shrink < s3->shrink + s3->wr + s2->wl - shrink + && !s3->un.note.invis + && (s3->ly != 0 + || (s2->ymn <= s3->ymx + 12 + && s2->ymx + 12 >= s3->ymn)) + ) { + s2->shrink = s3->shrink + s3->wr + s2->wl - shrink; + } else { +/*fixme: to do*/ + /* reduce shrink if s3 is far enough */ + } + + /* set the stretch value */ + s2->stretch = s2->x; + switch (s2->type) { + case NOTE: + if (s2->prv->type == GRACE) + break; + /* fall thru */ + case GRACE: + case REST: + s2->stretch *= 2.0; + break; + case BAR: + case MREST: + case MREP: + s2->stretch *= 1.4; + break; + } + + /* keep the symbol with larger space */ + if (s2->shrink > max_shrink) + max_shrink = s2->shrink; + if (s2->x > max_space) { + max_space = s2->x; + s4 = s2; + } + } + + /* make sure that space >= shrink */ + if (max_space < max_shrink) { + max_space = max_shrink; + if (s4->stretch < max_shrink) + s4->stretch = max_shrink; + } + + /* set the horizontal position goal */ + shrink += max_shrink; + space += max_space; + stretch += s4->stretch; + + /* adjust spacing and advance */ + s4 = s; /* (for overfull) */ + for ( ; s != s2; s = s->ts_next) { + voice = s->voice; + s->x = space; + s->shrink = shrink; + s->stretch = stretch; + + s3 = staff_further[s->staff]; + if (shrink + s->wr > s3->shrink + s3->wr) + staff_further[s->staff] = s; + + if (s->nxt != 0) { + + /* remove some double bars */ + if (s->nxt->type == BAR + && s->nxt->text == 0 + && s->nxt->nxt != 0 + && s->nxt->nxt->type == BAR + && s->nxt->nxt->text == 0) { + s3 = 0; + if ((s->nxt->un.bar.type == B_SINGLE + || s->nxt->un.bar.type == B_DOUBLE) + && (s->nxt->nxt->un.bar.type & 0xf0)) + s3 = s->nxt; + if (s3 != 0) { + s3->prv->nxt = s3->nxt; + if (s3->nxt != 0) + s3->nxt->prv = s3->prv; + s3->ts_prev->ts_next = s3->ts_next; + if (s3->ts_next != 0) + s3->ts_next->ts_prev = s3->ts_prev; + if (s3 == s2) + s2 = s3->ts_next; + } + } + + /* set spacing for the next symbol */ + set_spacing(s->nxt); + } + } + if (s == 0) + break; + + /* check the total width */ + if (cfmt.continueall) { + if (space <= width) + continue; + if ((space - width) / (space - shrink) < alfa0) + continue; + } else if (shrink < width) + continue; + /* may have a key sig change at end of line */ +/*fixme: may also have a meter change*/ + if (s->type == KEYSIG) + continue; + s = s4->ts_prev; + if (!cfmt.continueall) + alert( "Line %d overfull", s->linenum); + + /* go back to the previous bar, if any */ + for (s2 = s; s2 != 0; s2 = s2->ts_prev) { + if ((s2->type == BAR + && s2->un.bar.type != B_INVIS) + || s2->type == KEYSIG) + break; + } + + /* (should have some note) */ + if (s2 != 0 + && s2->time > tssym->time) + s = s2; + + /* don't cut in a grace note sequence if followed by note */ + if (s->type == GRACE + && s->nxt != 0 + && s->nxt->type == NOTE) + s = s->prv; + + /* restore the linkages */ + if (tsnext != 0) + tsnext->ts_prev->ts_next = tsnext; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + voice = p_voice - voice_tb; + for (s2 = tsnext; s2 != 0; s2 = s2->ts_next) { + if (s2->voice == voice) { + if (s2->prv != 0) + s2->prv->nxt = s2; + break; + } + } + } + set_piece(s); + break; + } + + for (s = tssym; s->ts_next != 0; s = s->ts_next) + ; + + /* get the total space from the last effective symbol */ + while (s->type == TEMPO + || s->type == STAVES) + s = s->ts_prev; + space = s->x; + stretch = s->stretch; + shrink = s->shrink; + + /* if the last symbol is not a bar, add some extra space */ + if (s->type != BAR) { + if (s->pr < s->wr) + s->pr = s->wr; + shrink += s->wr + 3.; + space += s->pr + 5.; + stretch += (s->pr + 5.) * 1.4; + } + + /* set the glue, calculate final symbol positions */ + alfa = beta = 0; + if (space > width) { + alfa = (space - width) / (space - shrink); + } else { + beta = (width - space) / (stretch - space); + if (beta > beta0) { + if (!cfmt.continueall) { + alert("Line %d underfull (%.0fpt of %.0fpt)", s->linenum, + beta0 * stretch + (1 - beta0) * space, + width); + } + if (!cfmt.stretchstaff) + beta = 0; + if (!cfmt.stretchlast + && tsnext == 0 /* if last line of tune */ + && beta >= beta_last) { + alfa = alfa_last; /* shrink underfull last line same as previous */ + beta = beta_last; + } + } + } + + w = alfa * shrink + beta * stretch + (1 - alfa - beta) * space; + if (alfa != 0) { + for (s = tssym; s != 0; s = s->ts_next) + s->x = alfa * s->shrink + (1. - alfa) * s->x; + } else { + for (s = tssym; s != 0; s = s->ts_next) + s->x = beta * s->stretch + (1. - beta) * s->x; + } + + /* add small random shifts to positions (if only one voice) */ + if (first_voice->next == 0) { + for (s = first_voice->sym; s->nxt != 0; s = s->nxt) { + if (s->len > 0) { /* if note or rest */ + float w1, w2; + + w1 = s->x - s->prv->x; + w2 = s->nxt->x - s->x; + if (w2 < w1) + w1 = w2; + s->x += RANFAC * ranf(-w1, w1); + } + } + } + + alfa_last = alfa; + beta_last = beta; + + realwidth = w; +} + +/* -- check if any "real" symbol in the piece -- */ +/* and adjust the scale and left and right margins */ +static int any_symbol(void) +{ + struct SYMBOL *s; + + for (s = first_voice->sym; s != 0; s = s->ts_next) { + switch (s->type) { + case NOTE: + case REST: + case MREST: + case BAR: + case MREP: + return 1; + } + } + return 0; +} + +/* -- find one line to output -- */ +static void find_piece(void) +{ + struct SYMBOL *s; + int number, time, seq, i, voice; + /* !! see /tw_head !! */ + static unsigned char pitw[12] = + {28, 54, 28, 54, 28, 28, 54, 28, 54, 28, 54, 28}; + + if (!cfmt.continueall) { + voice = first_voice - voice_tb; + if ((number = cfmt.barsperstaff) == 0) { + + /* find the first end-of-line */ + for (s = tssym; /*s != 0*/; s = s->ts_next) { + if (s->sflags & S_EOLN && s->voice == voice) + break; + if (s->ts_next == 0) { + /* when '\' at end of line and 'P:' */ +/* bug("no eoln in piece", 0); */ + break; + } + } + } else { + + /* count the measures */ + for (s = tssym; s->ts_next != 0; s = s->ts_next) + if (s->len > 0) /* if note or rest */ + break; + for ( ; s->ts_next != 0; s = s->ts_next) { + if (s->type != BAR + || s->un.bar.type == B_INVIS) + continue; + if (s->prv->type == BAR) + continue; + if (s->voice == voice + && --number <= 0) + break; + } + } + + /* cut at the last symbol of the sequence */ + time = s->time + s->len; + seq = s->seq; + if (s->len > 0) { + + /* note or rest: cut on end time */ + for (; s->ts_next != 0; s = s->ts_next) + if (s->ts_next->time >= time) + break; + } else { + + /* other symbol: cut at end of sequence */ + for (; s->ts_next != 0; s = s->ts_next) + if (s->ts_next->seq != seq) + break; + } + set_piece(s); + } else tsnext = 0; + + for (i = nstaff; i >= 0; i--) { + staff_tb[i].nvocal = 0; + staff_tb[i].y = 0; + } + + /* shift the first note if any tin whistle */ + for (i = 0; i < nwhistle; i++) { + struct VOICE_S *p_voice; + float w; + + p_voice = &voice_tb[whistle_tb[i].voice]; + if (p_voice != first_voice + && p_voice->prev == 0) + continue; + w = pitw[whistle_tb[i].pitch % 12]; + for (s = p_voice->sym; s != 0; s = s->nxt) { + if (s->type == NOTE) + break; + w -= s->wl + s->wr; + } + if (s == 0 || w < 0) + continue; + if (w > s->wl) + s->wl = w; + } +} + +/* -- init symbol list with clef, meter, key -- */ +static void init_music_line(struct VOICE_S *p_voice) +{ + struct SYMBOL *s, *sym; + + sym = p_voice->sym; + p_voice->sym = 0; + + /* output the first postscript sequences */ + if (sym != 0) { + while (sym->type == FMTCHG + && sym->u == PSSEQ) { + PUT1("%s\n", &sym->text[13]); + if (sym->nxt != 0) + sym->nxt->prv = 0; + if (sym->ts_next != 0) + sym->ts_next->ts_prev = sym->ts_prev; + if (sym->ts_prev != 0) + sym->ts_prev->ts_next = sym->ts_next; + if (tsnext == sym) + tsnext = sym->ts_next; + if ((sym = sym->nxt) == 0) + break; + } + } + + /* add clef */ + s = add_sym(p_voice, CLEF); // (parse.c) + memcpy(&p_voice->clef, &staff_tb[p_voice->staff].clef, + sizeof p_voice->clef); + memcpy(&s->un.clef, &p_voice->clef, + sizeof s->un.clef); + + /* add keysig */ + s = add_sym(p_voice, KEYSIG); + s->seq++; + memcpy(&s->un.key, &p_voice->key, sizeof s->un.key); + if (s->un.key.bagpipe && s->un.key.sf == 2) /* K:Hp */ + s->u = 3; /* --> Gnat */ + + /* add time signature if needed (from first voice) */ + if (insert_meter + && first_voice->meter.nmeter != 0) { /* != M:none */ + s = add_sym(p_voice, TIMESIG); + s->seq += 2; + memcpy(&s->un.meter, &first_voice->meter, + sizeof s->un.meter); + } + + /* add tempo if any */ + if (info.tempo) { + s = info.tempo; + p_voice->last_symbol->nxt = s; + s->prv = p_voice->last_symbol; + p_voice->last_symbol = s; + s->voice = p_voice - voice_tb; + s->staff = p_voice->staff; + s->type = TEMPO; + s->seq = s->prv->seq + 1; /*??*/ + info.tempo = 0; + } + + /* add bar if needed */ + if (p_voice->bar_start != 0) { + int i; + + i = 4; + if (p_voice->bar_text == 0 /* if repeat continuation */ + && p_voice->bar_start == B_OBRA) { + for (s = sym; s != 0; s = s->nxt) { /* search the end of repeat */ + if (s->type == BAR) { + if ((s->un.bar.type & 0xf0) /* if complex bar */ + || s->un.bar.type == B_CBRA + || s->un.bar.repeat_bar) + break; + if (--i < 0) + break; + } + } + if (s == 0 || sym == 0) + i = -1; + if (i >= 0 && sym->time == s->time) + i = -1; /* no note */ + } + if (i >= 0) { + s = add_sym(p_voice, BAR); + s->seq += 3; + s->un.bar.type = p_voice->bar_start; + s->text = p_voice->bar_text; + s->un.bar.repeat_bar = p_voice->bar_repeat; + } + p_voice->bar_start = 0; + p_voice->bar_repeat = 0; + p_voice->bar_text = 0; + } + if ((p_voice->last_symbol->nxt = sym) != 0) + sym->prv = p_voice->last_symbol; + p_voice->nvocal = 0; +} + +/* -- initialize a new line -- */ +static void cut_symbols(void) +{ + struct VOICE_S *p_voice; + struct SYMBOL *s, *s1; + int j, t; + + /* set start of voices */ + s1 = tsnext; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + int voice; + + p_voice->sym = 0; /* may have no symbol */ + voice = p_voice - voice_tb; + for (s = s1; s != 0; s = s->ts_next) { + if (s->voice == voice) { + p_voice->sym = s; +/* p_voice->staff = s->staff; */ + s->prv = 0; + break; + } + } + } + + /* add the first symbols of the line */ + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + init_music_line(p_voice); + + /* insert the new symbols into the time sorted list */ + p_voice->s_anc = p_voice->sym; + } + + s = 0; + t = s1->time; + for (j = 1; ; j++) { + int done; + + done = 1; + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + s1 = p_voice->s_anc; + if (s1 == 0 + || s1->ts_prev != 0) + continue; + done = 0; /* new symbol */ + if (s == 0) + tssym = s1; + else s->ts_next = s1; + s1->ts_prev = s; + s = s1; + s->seq = j; + s->time = t; + p_voice->s_anc = s->nxt; + set_width(s); /* set the width of the symbol */ + } + if (done) + break; + } + s->ts_next = tsnext; + if (tsnext != 0) + tsnext->ts_prev = s; +} + +/* -- adjust the values in the postscript buffer -- */ +static void buffer_adjust(void) +{ + int i; + float v_tb[MAXSTAFF]; + + for (i = 0; i <= nstaff; i++) + v_tb[i] = staff_tb[i].y; + set_buffer(v_tb); +} + +/* -- output for parsed symbol list -- */ +void output_music(void) +{ + struct VOICE_S *p_voice; + int voice, first_line; + float lwidth; + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + if (p_voice->sym != 0) + break; + if (p_voice == 0) + return; /* no symbol at all */ + + /* duplicate the voices appearing in many staves */ + voice_dup(); // (parse.c) + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + init_music_line(p_voice); + first_line = insert_meter; + insert_meter = 0; + + alfa_last = 0.1; + beta_last = 0; + + /* dump buffer if not enough space for a staff line */ + check_buffer(); // (buffer.c) + + /* set global characteristics */ + set_global(); + if (first_voice->next != 0) /* when multi-voices */ + set_multi(); // set the stems direction in 'multi' + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + set_beams(p_voice->sym); // decide on beams + set_stems(); /* set the stem lengths */ + set_overlap(); /* set note shift when voices overlap */ + set_sym_widths(); /* set the symbol widths */ + + /* set all symbols (per music line) */ + lwidth = ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin) + / cfmt.scale; + for (;;) { /* loop over pieces of line for output */ + find_piece(); + + if (any_symbol()) { + float indent, line_height; + + indent = set_indent(first_line); + set_sym_glue(lwidth - indent); + if (indent != 0) + PUT1("%.2f 0 T\n", indent); /* do indentation */ + draw_sym_near(); + line_height = set_staff(); + buffer_adjust(); + draw_staff(first_line, indent); + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) + draw_symbols(p_voice); // (draw.c) + draw_all_deco(); + bskip(line_height); + if (nwhistle != 0) + draw_whistle(); + if (indent != 0) + PUT1("%.2f 0 T\n", -indent); + buffer_eob(); + first_line = 0; + } + if (tsnext == 0) + break; + cut_symbols(); + } + + /* reset the parser */ + for (voice = MAXVOICE; --voice >= 0; ) { + voice_tb[voice].sym = 0; + voice_tb[voice].time = 0; + } +} + +/* -- reset the generator -- */ +void reset_gen(void) +{ + insert_meter = 1; +} diff --git a/src-abcm2ps/one-voice.fmt b/src-abcm2ps/one-voice.fmt new file mode 100644 index 0000000..8ecd456 --- /dev/null +++ b/src-abcm2ps/one-voice.fmt @@ -0,0 +1,26 @@ +% For single voice from multi-voice score + + scale 0.75 + titlefont Helvetica-Bold 18 + subtitlefont Times-Bold 16 + composerfont Times-Bold 12 + gchordfont Times-Roman 16 + topmargin 0.5cm + botmargin 0.0cm + leftmargin 1.0cm + rightmargin 1.0cm + titlespace 0.0cm + topspace 0.0cm + subtitlespace 0.0cm + composerspace 0.0cm + musicspace 0.0cm + parskipfac 1.0 + staffwidth 18.4cm + staffsep 45 + maxshrink 0.65 + + lineskipfac 1.1 + parskipfac 0 + textspace 0.2cm + textfont Times-Roman 10 + gchordbox 1 diff --git a/src-abcm2ps/parse.cpp b/src-abcm2ps/parse.cpp new file mode 100644 index 0000000..8c9029c --- /dev/null +++ b/src-abcm2ps/parse.cpp @@ -0,0 +1,1766 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +struct STAFF staff_tb[MAXSTAFF]; /* staff table */ +int nstaff; /* (0..MAXSTAFF-1) */ + +struct lyric_fonts_s lyric_fonts[8]; +int nlyric_font; + +struct VOICE_S voice_tb[MAXVOICE]; /* voice table */ +int nvoice; /* (0..MAXVOICE-1) */ +static struct VOICE_S *curvoice; /* current voice while parsing */ +struct VOICE_S *first_voice; /* first voice */ + +static struct FORMAT dfmt; /* format at start of tune */ + +static int lyric_nb; /* current number of lyric lines */ +static struct SYMBOL *lyric_start; /* 1st note of the line for w: */ +static struct SYMBOL *lyric_cont; /* current symbol when w: continuation */ + +static struct SYMBOL *grace_head, *grace_tail; +static struct SYMBOL *voice_over; /* voice overlay */ +static int over_bar; /* voice overlay in a measure */ +static int staves_found; + +static int bar_number; /* (for %%setbarnb) */ + +float multicol_start; /* (for multicol) */ +static float multicol_max; +static float lmarg, rmarg; + +#define SQ_ANY 0x00 +#define SQ_CLEF 0x10 +#define SQ_SIG 0x20 +#define SQ_GRACE 0x30 +#define SQ_BAR 0x40 +#define SQ_EXTRA 0x50 +#define SQ_NOTE 0x70 +static char seq_tb[14] = { /* sequence # indexed by symbol type */ + SQ_EXTRA, SQ_NOTE, SQ_NOTE, SQ_BAR, SQ_CLEF, + SQ_SIG, SQ_SIG, SQ_ANY, SQ_ANY, SQ_NOTE, + SQ_ANY, SQ_NOTE, SQ_GRACE, SQ_ANY +}; + +static void get_clef(struct SYMBOL *s); +static void get_key(struct SYMBOL *s); +static void get_meter(struct SYMBOL *s); +static void get_voice(struct SYMBOL *s); +static void get_note(struct SYMBOL *s); +static struct SYMBOL *process_pscomment(struct SYMBOL *as); +static void set_nplet(struct SYMBOL *s); +static void sym_link(struct SYMBOL *s); + +/* -- add a new symbol at end of list -- */ +struct SYMBOL *add_sym(struct VOICE_S *p_voice, + int type) +{ + struct SYMBOL *s; + + s = (struct SYMBOL *) malloc(sizeof *s); + memset(s, 0, sizeof *s); + if (p_voice->sym != 0) { + p_voice->last_symbol->nxt = s; + s->prv = p_voice->last_symbol; + } else p_voice->sym = s; + p_voice->last_symbol = s; + + s->type = type; + s->seq = seq_tb[type]; + s->voice = p_voice - voice_tb; + s->staff = p_voice->staff; + return s; +} + +/* -- insert a symbol after a reference one -- */ +struct SYMBOL *ins_sym(int type, + struct SYMBOL *s) /* previous symbol */ +{ + struct VOICE_S *p_voice; + struct SYMBOL *new_s, *next; + + curvoice = p_voice = &voice_tb[s->voice]; + p_voice->last_symbol = s; + next = s->nxt; + new_s = add_sym(p_voice, type); + if ((new_s->nxt = next) != 0) + next->prv = new_s; + return new_s; +} + +/* -- duplicate the symbols of the voices appearing in many staves -- */ +void voice_dup(void) +{ + struct VOICE_S *p_voice, *p_voice1; + struct SYMBOL *s, *s2; + + for (p_voice = first_voice; p_voice; p_voice = p_voice->next) { + int voice; + + if (p_voice->clone < 0) + continue; + voice = p_voice - voice_tb; + p_voice1 = &voice_tb[p_voice->clone]; + p_voice->name = p_voice1->name; + for (s = p_voice1->sym; + s != 0; + s = s->nxt) { + s2 = (struct SYMBOL *) malloc(sizeof *s2); + memcpy(s2, s, sizeof *s2); + if (p_voice->sym != 0) { + p_voice->last_symbol->nxt = s2; + s2->prv = p_voice->last_symbol; + } else p_voice->sym = s2; + p_voice->last_symbol = s2; + + s2->voice = voice; + s2->staff = p_voice->staff; + s2->ly = 0; + } + } +} + +/* -- change the accidentals and "\\n" in the guitar chords -- */ +static void gchord_adjust(struct SYMBOL *s) +{ + char *p; + int freegchord, l; + + freegchord = cfmt.freegchord; + p = s->text; + if (strchr("^_<>@", *p) != 0) + freegchord = 1; /* annotation */ +/*fixme: treat 'dim' as 'o', 'halfdim' as '/o', and 'maj' as a triangle*/ + while (*p != '\0') { + switch (*p) { + case '#': + if (!freegchord) + *p = '\201'; + break; + case 'b': + if (!freegchord) + *p = '\202'; + break; + case '=': + if (!freegchord) + *p = '\203'; + break; + case ';': + *p = '\n'; /* abcMIDI compatibility */ + break; + case '\\': + p++; + switch (*p) { + case '\0': + return; + case 'n': + p[-1] = '\n'; + goto move; + case '#': + p[-1] = '\201'; + goto move; + case 'b': + p[-1] = '\202'; + goto move; + case '=': + p[-1] = '\203'; + move: + l = strlen(p); + memmove(p, p + 1, l); + p--; + break; + } + break; + } + p++; + } +} + +/* -- parse a lyric (vocal) definition -- */ +static const char *get_lyric(const char *p) +{ + struct SYMBOL *s; + char word[81], *w; + int ln; + int curfont; + + /* search/create the lyric font */ + for (curfont = 0; curfont < nlyric_font; curfont++) { + if (lyric_fonts[curfont].font == cfmt.vocalfont.fnum + && lyric_fonts[curfont].size == cfmt.vocalfont.size) + break; + } + if (curfont >= nlyric_font) { + if (curfont >= sizeof lyric_fonts / sizeof lyric_fonts[0]) + return "Too many lyric fonts"; + lyric_fonts[curfont].font = cfmt.vocalfont.fnum; + lyric_fonts[curfont].size = cfmt.vocalfont.size; + nlyric_font++; + } + + if ((s = lyric_cont) == 0) { + if (lyric_nb >= MAXLY) + return "Too many lyric lines"; + ln = lyric_nb++; + s = lyric_start; + } else { + lyric_cont = 0; + ln = lyric_nb - 1; + } + + /* scan the lyric line */ + while (*p != '\0') { + while (isspace(*p)) + p++; + if (*p == '\0') + break; + switch (*p) { + case '|': + while (s != 0 && (s->type != BAR + || s->un.bar.type == B_INVIS)) + s = s->nxt; + if (s == 0) + return "Not enough bar lines for lyric line"; + s = s->nxt; + p++; + continue; + case '-': + word[0] = '\x02'; + word[1] = '\0'; + p++; + break; + case '_': + word[0] = '\x03'; + word[1] = '\0'; + p++; + break; + case '*': + word[0] = *p++; + word[1] = '\0'; + break; + case '\\': + if (p[1] == '\0') { + lyric_cont = s; + return 0; + } + /* fall thru */ + default: + w = word; + for (;;) { + char c; + + c = *p; + switch (c) { + case '\0': + case ' ': + case '\t': + case '_': + case '*': + case '|': + break; + case '~': + c = ' '; + goto addch; + case '-': + c = '\x02'; + goto addch; + case '\\': + if (p[1] == '\0') + break; + switch (p[1]) { + case '~': + if (w < &word[sizeof word - 1]) + *w++ = c; + /* fall thru */ + case '_': + case '*': + case '|': + case '-': + p++; + c = *p; + break; + } + /* fall thru */ + default: + addch: + if (w < &word[sizeof word - 1]) + *w++ = c; + p++; + if (c == '\x02') + break; + continue; + } + break; + } + *w = '\0'; + break; + } + + /* store word in next note */ + while (s != 0 && s->type != NOTE) + s = s->nxt; + if (s == 0) + return "Not enough notes for lyric line"; + if (word[0] != '*') { + int l; + + if (s->ly == 0) { + s->ly = (struct lyrics *) malloc(sizeof (struct lyrics)); + memset(s->ly, 0, sizeof (struct lyrics)); + } + l = strlen(word) + 1 + 1; + w = (char*)malloc(l); + s->ly->w[ln] = w; + w[0] = curfont; /* the 1st char is the font index */ + strcpy(w + 1, word); + } + s = s->nxt; + } + while (s != 0 && s->type != NOTE) + s = s->nxt; + if (s != 0) + return "Not enough words for lyric line"; + return 0; +} + +/* -- get a voice overlay -- */ +static void get_over(struct SYMBOL *s) +{ + struct VOICE_S *p_voice, *p_voice2; + struct SYMBOL *s1, *s2; + int ctime, otype, linenum; + + /* treat the end of overlay */ + p_voice = curvoice; + linenum = s != 0 ? s->linenum : 0; + if (over_bar) { + s2 = add_sym(curvoice, BAR); + s2->sym_type = ABC_T_BAR; + s2->linenum = linenum; + s2->time = curvoice->time; + s2->seq = SQ_BAR; + } + if (s == 0 + || s->un.v_over.type == V_OVER_E) { + over_bar = 0; + if (voice_over == 0) { + alert("Erroneous end of voice overlap (line %d)", s->linenum); + return; + } + voice_over = 0; + for (p_voice = p_voice->prev; ; p_voice = p_voice->prev) + if (p_voice->name[0] != '&') + break; + curvoice = p_voice; + return; + } + + /* treat the overlay start */ + otype = s->un.v_over.type; + if (otype == V_OVER_SS + || otype == V_OVER_SD) { + voice_over = s; + return; + } + + /* create the extra voice if not done yet */ + p_voice2 = &voice_tb[s->un.v_over.voice]; + if (p_voice2->name == 0) { + p_voice2->name = "&"; + p_voice2->second = 1; + p_voice2->staff = p_voice->staff; +#if 0 + memcpy(&p_voice2->clef, &p_voice->clef, + sizeof p_voice2->clef); + memcpy(&p_voice2->meter, &p_voice->meter, + sizeof p_voice2->meter); +#endif + if ((p_voice2->next = p_voice->next) != 0) + p_voice2->next->prev = p_voice2; + p_voice->next = p_voice2; + p_voice2->prev = p_voice; + if (otype == V_OVER_S) + p_voice2->stem = 1; /*fixme: may be down*/ + } + + /* search the start of sequence */ + ctime = p_voice2->time; + if (voice_over == 0) { + voice_over = s; + over_bar = 1; + for (s = p_voice->last_symbol; /*s != 0*/; s = s->prv) + if (s->type == BAR || s->time <= ctime) + break; + } else { + struct SYMBOL *tmp; + + tmp = s; + s = (struct SYMBOL *) voice_over->sym_next; +/*fixme: what if this symbol is not in the voice?*/ + if (s->voice != curvoice - voice_tb) { + alert("Voice overlay not closed (line %d)", s->linenum); + voice_over = 0; + return; + } + voice_over = tmp; + } + + /* search the last common sequence */ + for (s1 = s; /*s1 != 0*/; s1 = s1->prv) + if (s1->time <= ctime) + break; + + /* fill the secundary voice with invisible silences */ + if (p_voice2->last_symbol == 0 + || p_voice2->last_symbol->type != BAR) { + for (s2 = s1; s2 != 0 && s2->time == ctime; s2 = s2->prv) { + if (s2->type == BAR) { + s1 = s2; + break; + } + } + if (s1->type == BAR) { + s2 = add_sym(p_voice2, BAR); + s2->linenum = linenum; + s2->un.bar.type = s1->un.bar.type; + s2->time = ctime; + s2->seq = SQ_BAR; + } + } + while (ctime < s->time) { + while (s1->time < s->time) { + s1 = s1->nxt; + if (s1->type == BAR) + break; + } + if (s1->time - ctime != 0) { + s2 = add_sym(p_voice2, REST); + s2->sym_type = ABC_T_REST; + s2->linenum = linenum; + s2->un.note.invis = 1; + s2->len = s2->un.note.lens[0] = s1->time - ctime; + s2->time = ctime; + s2->seq = SQ_NOTE; + ctime = s1->time; + } + while (s1->type == BAR) { + s2 = add_sym(p_voice2, BAR); + s2->linenum = linenum; + s2->un.bar.type = s1->un.bar.type; + s2->time = ctime; + s2->seq = SQ_BAR; + if ((s1 = s1->nxt) == 0) + break; + } + } + p_voice2->time = ctime; + curvoice = p_voice2; +} + +/* -- get staves definition (%%staves) -- */ +static void get_staves(struct SYMBOL *s) +{ + int i, staff, flags; + struct staff_s *p_staff; + struct VOICE_S *p_voice, *p_voice2; + int dup_voice; + + /* clear, then link the voices */ + for (i = 0, p_voice = voice_tb; + i < MAXVOICE; + i++, p_voice++) { + p_voice->clone = -1; + p_voice->next = 0; + p_voice->prev = 0; + p_voice->second = 0; + p_voice->floating = 0; + } + + p_voice2 = 0; + dup_voice = MAXVOICE; + for (i = 0, p_staff = s->un.staves; + i < MAXVOICE && p_staff->name; + i++, p_staff++) { + int voice; + + voice = p_staff->voice; + p_voice = &voice_tb[voice]; + if (voice > nvoice) + nvoice = voice; + + /* if voice already inserted, duplicate it */ + if (p_voice == p_voice2 || p_voice->next || p_voice->prev) { + struct VOICE_S *p_voice3; + + dup_voice--; + p_voice3 = &voice_tb[dup_voice]; + memcpy(p_voice3, p_voice, sizeof *p_voice3); + p_voice3->clone = voice; + p_voice3->next = 0; + p_voice3->second = 0; + p_voice3->floating = 0; + p_voice = p_voice3; + p_staff->voice = dup_voice; + } + + p_voice->name = p_staff->name; + + /* link the voices */ + if ((p_voice->prev = p_voice2) == 0) + first_voice = p_voice; + else p_voice2->next = p_voice; + p_voice2 = p_voice; + } + + /* define the staves */ + memset(staff_tb, 0, sizeof staff_tb); + for (i = MAXSTAFF; --i >= 0; ) + staff_tb[i].clef.line = 2; /* treble clef on 2nd line */ + staff = -1; + for (i = 0, p_staff = s->un.staves; + i < MAXVOICE && p_staff->name; + i++, p_staff++) { + int v; + + flags = p_staff->flags; +#if MAXSTAFF < MAXVOICE + if (staff >= MAXSTAFF - 1) { + alert("Too many staves (line %d)",s->linenum); + return; + } +#endif + staff++; + + p_voice = &voice_tb[p_staff->voice]; + + p_voice->staff = staff; + if (p_voice->forced_clef) { +/*fixme*/ + memcpy(&staff_tb[staff].clef, &p_voice->clef, + sizeof staff_tb[0].clef); + } + if (flags & STOP_BAR) + staff_tb[staff].stop_bar = 1; + if (flags & OPEN_BRACKET) + staff_tb[staff].bracket = 1; + if (flags & CLOSE_BRACKET) + staff_tb[staff].bracket_end = 1; + if (flags & OPEN_BRACE) { + for (v = i + 1; v < MAXVOICE; v++) + if (s->un.staves[v].flags & CLOSE_BRACE) + break; + switch (v - i) { + case 1: /* {a b} */ + if (flags & OPEN_PARENTH) + goto err; + break; + case 2: /* {a b c} */ + if (flags & OPEN_PARENTH + || (p_staff[1].flags & OPEN_PARENTH)) + break; + i++; + p_staff++; + p_voice = &voice_tb[p_staff->voice]; + p_voice->second = 1; + p_voice->floating = 1; + p_voice->staff = staff; + break; + case 3: /* {a b c d} */ + if (flags & OPEN_PARENTH + && (p_staff[2].flags & OPEN_PARENTH)) + break; + if (flags & OPEN_PARENTH + || (p_staff[1].flags & OPEN_PARENTH) + || (p_staff[2].flags & OPEN_PARENTH)) + break; + /* -> {(a b) (c d)} */ + p_staff->flags |= OPEN_PARENTH; + flags |= OPEN_PARENTH; + p_staff[1].flags |= CLOSE_PARENTH; + p_staff[2].flags |= OPEN_PARENTH; + p_staff[3].flags |= CLOSE_PARENTH; + break; + default: + goto err; + } + staff_tb[staff].brace = 1; + } + if (flags & CLOSE_BRACE) + staff_tb[staff].brace_end = 1; + if (flags & OPEN_PARENTH) { + while (i < MAXVOICE) { + i++; + p_staff++; + p_voice = &voice_tb[p_staff->voice]; + p_voice->second = 1; + p_voice->staff = staff; + if (p_staff->flags & CLOSE_PARENTH) + break; + } + if (p_staff->flags & STOP_BAR) + staff_tb[staff].stop_bar = 1; + if (p_staff->flags & CLOSE_BRACKET) + staff_tb[staff].bracket_end = 1; + if (p_staff->flags & CLOSE_BRACE) { + staff_tb[staff].brace_end = 1; + + /* the lower voice must be main */ + if (p_voice->second) { + p_voice->second = 0; + do { + p_voice = p_voice->prev; + } while (p_voice->second); + p_voice->second = 1; + } + } + } + } + nstaff = staff; + return; + + /* when error, let one voice per staff */ +err: + alert("%%%%staves error (line %d)",s->linenum); + for (p_voice = voice_tb, staff = 0; + p_voice != 0; + p_voice = p_voice->next, staff++) + p_voice->staff = staff; + nstaff = staff; +} + +/* -- initialize the general tune characteristics of all potential voices -- */ +static void voice_init(void) +{ + struct VOICE_S *p_voice; + int i; + + for (i = 0, p_voice = voice_tb; + i < MAXVOICE; + i++, p_voice++) { + p_voice->sym = 0; + p_voice->clone = -1; + p_voice->bar_start = 0; + p_voice->time = 0; + p_voice->r_plet = 0; + } +} + +/* -- identify info line, store in proper place -- */ +static const char *state_txt[4] = { + "global", "header", "tune", "embedded" +}; +static void get_info(struct SYMBOL *s, + int info_type, + const char *p) +{ + struct ISTRUCT *inf; + + /* change global or local */ + inf = s->state == ABC_S_GLOBAL ? &default_info : &info; + + while (isspace(*p)) + p++; + + switch (info_type) { + case 'A': + inf->area = p; + return; + case 'B': + inf->book = p; + return; + case 'C': + if (inf->ncomp >= NCOMP) + alert("Too many composer lines (line %d)", s->linenum); + else { + inf->comp[inf->ncomp] = p; + inf->ncomp++; + } + return; + case 'D': + add_text(p, TEXT_D); + return; + case 'd': + case 'E': + case 'F': + case 'G': + return; + case 'H': + add_text(p, TEXT_H); + return; + case 'I': + return; + case 'K': + get_key(s); + if (s->state != ABC_S_HEAD) + return; + tunenum++; + PUT2("\n%% --- %s (%s) ---\n", + info.xref, info.title[0]); + if (!epsf) + bskip(cfmt.topspace); + write_heading(); + reset_gen(); + nbar = nbar_rep = cfmt.measurefirst; /* measure numbering */ + curvoice = first_voice; /* switch to the 1st voice */ + return; + case 'L': + return; + case 'M': + get_meter(s); + return; + case 'N': + add_text(p, TEXT_N); + return; + case 'O': + inf->orig = p; + return; + case 'P': + switch (s->state) { + case ABC_S_GLOBAL: + case ABC_S_HEAD: + inf->parts = p; + break; + case ABC_S_TUNE: { + struct VOICE_S *p_voice; + + p_voice = curvoice; + curvoice = first_voice; + sym_link(s); + s->type = PART; + curvoice = p_voice; + break; + } + default: + sym_link(s); + s->type = PART; + break; + } + return; + case 'Q': + if (curvoice != first_voice /* tempo only for first voice */ + || !cfmt.printtempo) + return; + switch (s->state) { + case ABC_S_GLOBAL: + case ABC_S_HEAD: + inf->tempo = s; + break; + default: + sym_link(s); + s->type = TEMPO; + break; + } + return; + case 'R': + inf->rhyth = p; + return; + case 'S': + inf->src = p; + return; + case 'T': + switch (s->state) { + default: + if (inf->ntitle >= 3) { + alert("Too many T: (line %d)",s->linenum); + return; + } + break; + case ABC_S_GLOBAL: /* new tune */ + if (!epsf) + write_buffer(); + dfmt = cfmt; + memcpy(&info, &default_info, sizeof info); + inf = &info; + inf->xref = p; + memcpy(&deco_tune, &deco_glob, sizeof deco_tune); + voice_init(); + break; + case ABC_S_TUNE: + inf->ntitle = 1; + break; + } + inf->title[inf->ntitle++] = p; + if (s->state != ABC_S_TUNE) + return; + output_music(); + write_title(inf->ntitle - 1); + bskip(cfmt.musicspace + 0.2 * CM); + voice_init(); + reset_gen(); /* (display the time signature) */ + curvoice = first_voice; + return; + case 'U': { + char *deco; + + deco = s->state == ABC_S_GLOBAL ? deco_glob : deco_tune; + deco[s->un.user.symbol] = deco_intern(s->un.user.value); + return; + } + case 'u': + return; + case 'V': + get_voice(s); + return; + case 'w': + if (cfmt.musiconly) + return; + if (s->state != ABC_S_TUNE + || lyric_start == 0) + break; + if ((p = get_lyric(p)) != 0) + alert("%s (line %d)", p,s->linenum); + return; + case 'W': + add_text(p, TEXT_W); + return; + case 'X': + if (!epsf) + write_buffer(); /* flush stuff left from %% lines */ + dfmt = cfmt; /* save the format at start of tune */ + memcpy(&info, &default_info, sizeof info); + info.xref = p; + memcpy(&deco_tune, &deco_glob, sizeof deco_tune); + voice_init(); /* initialize all the voices */ + return; + case 'Z': + add_text(p, TEXT_Z); + return; + } + alert( "%s info '%c:' not treated (line %d)", + state_txt[(int) s->state], info_type,s->linenum); +} + +/* -- set head type, dots, flags for note -- */ +void identify_note(struct SYMBOL *s, + int len, + int *p_head, + int *p_dots, + int *p_flags) +{ + int head, dots, flags, base; + + head = H_FULL; + flags = 0; + for (base = BREVE * 2; base > 0; base >>= 1) { + if (len >= base) + break; + } + if (len >= BREVE * 4) { + alert( "Note too long (line %d)",s->linenum); + len = base = BREVE * 2; + } + switch (base) { + case BREVE * 2: + head = H_SQUARE; + flags = -4; + break; + case BREVE: + head = cfmt.squarebreve ? H_SQUARE : H_OVAL; + flags = -3; + break; + case SEMIBREVE: + head = H_OVAL; + flags = -2; + break; + case MINIM: + head = H_EMPTY; + flags = -1; + break; + case CROTCHET: + break; + case QUAVER: + flags = 1; + break; + case SEMIQUAVER: + flags = 2; + break; + case SEMIQUAVER / 2: + flags = 3; + break; + case SEMIQUAVER / 4: + flags = 4; + break; + default: + alert( "Note too short (line %d)",s->linenum); + len = base = SEMIQUAVER / 4; + flags = 4; + break; + } + + dots = 0; + if (len == base) + ; + else if (2 * len == 3 * base) + dots = 1; + else if (4 * len == 7 * base) + dots = 2; + else if (8 * len == 15 * base) + dots = 3; + else alert( "Note too much dotted (line %d)",s->linenum); + + *p_head = head; + *p_dots = dots; + *p_flags = flags; +} + +/* -- measure bar -- */ +static void get_bar(struct SYMBOL *s) +{ + int bar_type; + struct SYMBOL *s2; + + bar_type = s->un.bar.type; + + /* remove the invisible repeat bars of the 1st voice */ + if (curvoice == first_voice + && bar_type == B_INVIS) { + s2 = curvoice->last_symbol; + if (s2 != 0 && s2->type == BAR + && s2->text == 0) { + s2->text = s->text; + s2->un.bar.repeat_bar = s->un.bar.repeat_bar; + if (s->sflags & S_EOLN) + s2->sflags |= S_EOLN; + return; + } + } + + /* merge back-to-back repeat bars */ + if (bar_type == B_LREP && s->text == 0) { + s2 = curvoice->last_symbol; + if (s2 != 0 && s2->type == BAR + && s2->un.bar.type == B_RREP) { + s2->un.bar.type = B_DREP; + if (s->sflags & S_EOLN) + s2->sflags |= S_EOLN; + return; + } + } + + sym_link(s); + s->type = BAR; + + if ((bar_type & 0xf0) != 0) { + do { + bar_type >>= 4; + } while ((bar_type & 0xf0) != 0); + if (bar_type == B_COL) + s->sflags |= S_RRBAR; + } + + if (bar_number != 0 + && curvoice == first_voice) { + s->u = bar_number; + bar_number = 0; + } + + /* the bar must be before a key signature */ + if (s->prv != 0 + && s->prv->type == KEYSIG) { + s2 = s->prv; + curvoice->last_symbol = s2; + s2->nxt = 0; + s2->prv->nxt = s; + s->prv = s2->prv; + s->nxt = s2; + s2->prv = s; + } + + /* convert the decorations */ + if (s->un.bar.dc.n > 0) + deco_cnv(&s->un.bar.dc, s); + + /* adjust the guitar chords */ + if (s->text != 0 && !s->un.bar.repeat_bar) + gchord_adjust(s); +} + +/* -- do a tune -- */ +void do_tune(struct abctune *at, + int header_only) +{ + int i; + + /* initialize */ + memset(voice_tb, 0, sizeof voice_tb); + voice_init(); /* initialize all the voices */ + voice_tb[0].name = "1"; /* implicit voice */ + voice_over = 0; + clear_text(); + nvoice = 0; + nstaff = 0; + memset(staff_tb, 0, sizeof staff_tb); + staves_found = 0; + for (i = MAXVOICE; --i >= 0; ) { + voice_tb[i].clef.line = 2; /* treble clef on 2nd line */ + voice_tb[i].meter.nmeter = 1; + voice_tb[i].meter.wmeasure = BASE_LEN; + voice_tb[i].meter.meter[0].top[0] = '4'; + voice_tb[i].meter.meter[0].bot[0] = '4'; + } + for (i = MAXSTAFF; --i >= 0; ) + staff_tb[i].clef.line = 2; + curvoice = first_voice = voice_tb; + use_buffer = !cfmt.splittune; + + /* scan the tune */ + grace_head = 0; + for (SYMBOL *s = at->first_sym; s != 0; s = s->sym_next) { + if (header_only + && s->state != ABC_S_GLOBAL) + break; + if (grace_head != 0 && s->sym_type != ABC_T_NOTE) + grace_head = 0; + switch (s->sym_type) { + case ABC_T_INFO: { + int info_type; + char *p; + + if (header_only + && (s->text[0] == 'X' + || s->text[0] == 'T')) + break; + info_type = s->text[0]; + p = &s->text[2]; + for (;;) { + get_info(s, info_type, p); + if (s->sym_next == 0 + || s->sym_next->sym_type != ABC_T_INFO2) + break; + s = (SYMBOL*)s->sym_next; + p = s->text; + } + break; + } + case ABC_T_PSCOM: + s = (SYMBOL*)process_pscomment(s); + break; + case ABC_T_NOTE: + case ABC_T_REST: + get_note(s); + break; + case ABC_T_BAR: + if (over_bar) + get_over(0); + get_bar(s); + break; + case ABC_T_CLEF: + get_clef(s); + break; + case ABC_T_EOLN: + if (curvoice->last_symbol != 0) + curvoice->last_symbol->sflags |= S_EOLN; + continue; + case ABC_T_MREST: + case ABC_T_MREP: { + int len; + + len = curvoice->meter.wmeasure * s->un.bar.len; + if (s->sym_type == ABC_T_MREP + && s->un.bar.len > 1) { + struct SYMBOL *s2; + + /* repeat measure more than 1 time */ + /* 2 times -> (bar - invisible rest - bar - mrep - bar) */ +/*fixme: 3 or more times not treated*/ + s2 = add_sym(curvoice, REST); + s2->sym_type = ABC_T_REST; + s2->linenum = s->linenum; + s2->un.note.invis = 1; + len /= s->un.bar.len; + s2->len = len; + s2->time = curvoice->time; + curvoice->time += len; + s2 = add_sym(curvoice, BAR); + s2->linenum = s->linenum; + s2->un.bar.type = B_SINGLE; + s2->time = curvoice->time; + } + sym_link(s); + s->type = s->sym_type == ABC_T_MREST ? MREST : MREP; + s->len = len; + break; + } + case ABC_T_V_OVER: + get_over(s); + continue; + default: + continue; + } + s->seq = seq_tb[s->type]; + s->time = curvoice->time; + if (grace_head == 0) + curvoice->time += s->len; + } + + output_music(); // start at first_voice (music.c) + put_words(); + if (cfmt.writehistory) + put_history(); + if (epsf && nbuf > 0) + write_eps(); + else write_buffer(); + + if (info.xref != 0) + cfmt = dfmt; /* restore the format at start of tune */ +} + +/* -- get a clef definition (in K: or V:) -- */ +static void get_clef(struct SYMBOL *s) +{ + struct VOICE_S *p_voice; + struct SYMBOL *s2; + + p_voice = curvoice; + if (s->sym_prev->sym_type == ABC_T_INFO) { + switch (s->sym_prev->text[0]) { + case 'K': + if (s->sym_prev->state == ABC_S_HEAD) { + int i; + + for (i = MAXVOICE, p_voice = voice_tb; + --i >= 0; + p_voice++) { + memcpy(&p_voice->clef, &s->un.clef, + sizeof p_voice->clef); + p_voice->forced_clef = 1; + } + for (i = MAXSTAFF; --i >= 0; ) + memcpy(&staff_tb[i].clef, &s->un.clef, + sizeof s->un.clef); + return; + } + break; + case 'V': /* clef relative to a voice definition */ + p_voice = &voice_tb[(int) s->sym_prev->un.voice.voice]; + break; + } + } + + if (p_voice->sym == 0) { + memcpy(&staff_tb[p_voice->staff].clef, &s->un.clef, /* initial clef */ + sizeof s->un.clef); + } else { +#if 0 + if (p_voice->clef.type != s->un.clef.type + || p_voice->clef.line != s->un.clef.line + || p_voice->clef.octave != s->un.clef.octave) { +#endif + sym_link(s); + s->type = CLEF; + s->u = 1; /* small clef */ + + /* the clef change must be before a key signature */ + s2 = s->prv; + if (s2->type == KEYSIG) { + s2->nxt = 0; + p_voice->last_symbol = s2; + s->prv = s2->prv; + if (s->prv != 0) + s->prv->nxt = s; + s->nxt = s2; + s2->prv = s; + } + + /* the clef change must be before a bar */ + s2 = s->prv; + if (s2 != 0 + && s2->type == BAR) { + s2->nxt = s->nxt; + if (s->nxt != 0) + s->nxt->prv = s2; + else p_voice->last_symbol = s2; + s->prv = s2->prv; + if (s->prv != 0) + s->prv->nxt = s; + s->nxt = s2; + s2->prv = s; + } +/* } */ + } + memcpy(&p_voice->clef, &s->un.clef, /* current clef */ + sizeof p_voice->clef); + p_voice->forced_clef = 1; /* don't change */ +} + +/* -- get a key signature definition (K:) -- */ +static void get_key(struct SYMBOL *s) +{ + struct VOICE_S *p_voice; + int i; + + if (s->un.key.empty) + return; /* clef only */ + switch (s->state) { + case ABC_S_HEAD: { + for (i = MAXVOICE, p_voice = voice_tb; + --i >= 0; + p_voice++) { + memcpy(&p_voice->key, &s->un.key, sizeof p_voice->key); + p_voice->sfp = s->un.key.sf; + } + break; + } + case ABC_S_TUNE: + case ABC_S_EMBED: + if (curvoice->sym == 0) { + memcpy(&curvoice->key, &s->un.key, sizeof curvoice->key); + curvoice->sfp = s->un.key.sf; + break; + } + sym_link(s); + s->type = KEYSIG; + s->u = curvoice->sfp; /* old key signature */ + curvoice->sfp = s->un.key.sf; + break; + } +} + +/* -- set meter from M: -- */ +static void get_meter(struct SYMBOL *s) +{ + switch (s->state) { + case ABC_S_GLOBAL: + /*fixme: keep the values and apply to all tunes?? */ + break; + case ABC_S_HEAD: { + struct VOICE_S *p_voice; + int i; + + for (i = MAXVOICE, p_voice = voice_tb; + --i >= 0; + p_voice++) + memcpy(&p_voice->meter, &s->un.meter, + sizeof curvoice->meter); + break; + } + case ABC_S_TUNE: + case ABC_S_EMBED: + if (curvoice->sym == 0) { + memcpy(&curvoice->meter, &s->un.meter, + sizeof curvoice->meter); + reset_gen(); /* (display the time signature) */ + break; + } + if (s->un.meter.nmeter == 0) + break; /* M:none */ + sym_link(s); + s->type = TIMESIG; + break; + } +} + +/* -- treat a 'V:' -- */ +static void get_voice(struct SYMBOL *s) +{ + int voice; + struct VOICE_S *p_voice; + + voice = s->un.voice.voice; + p_voice = &voice_tb[voice]; + if (voice > nvoice) { /* new voice */ + struct VOICE_S *p_voice2; + + nvoice = voice; + if (!staves_found) { + if (!s->un.voice.merge) { +#if MAXSTAFF < MAXVOICE + if (nstaff >= MAXSTAFF - 1) { + alert( "Too many staves (line %d)",s->linenum); + return; + } +#endif + nstaff++; + } else p_voice->second = 1; + p_voice->staff = nstaff; + for (p_voice2 = first_voice; + p_voice2->next != 0; + p_voice2 = p_voice2->next) + ; + p_voice2->next = p_voice; + p_voice->prev = p_voice2; + } else p_voice->staff = nstaff + 1; + } + + /* if in tune, switch to this voice */ + switch (s->state) { + case ABC_S_TUNE: + case ABC_S_EMBED: + curvoice = p_voice; + break; + } + + /* if something has changed, update */ + if (s->un.voice.name != 0) + p_voice->name = s->un.voice.name; + if (s->un.voice.fname != 0) + p_voice->nm = s->un.voice.fname; + if (s->un.voice.nname != 0) + p_voice->snm = s->un.voice.nname; + if (s->un.voice.stem != 0) + p_voice->stem = s->un.voice.stem; +} + +/* -- note or rest -- */ +static void get_note(struct SYMBOL *s) +{ + s->nhd = s->un.note.nhd; + if (!s->un.note.grace) { /* normal note/rest */ + if (grace_head != 0) + grace_head = 0; + sym_link(s); + s->multi = curvoice->stem; + } else { /* in a grace note sequence */ + int i, div; + + if (grace_head == 0) { + struct SYMBOL *s2; + + s2 = add_sym(curvoice, GRACE); + s2->type = GRACE; + s2->linenum = s->linenum; + s2->time = curvoice->time; + grace_head = s2; + grace_head->grace = grace_tail = s; + } else { + grace_tail->nxt = s; + s->prv = grace_tail; + grace_tail = s; + } + s->voice = curvoice - voice_tb; + s->staff = curvoice->staff; + + /* adjust the grace note lengths */ + if (!curvoice->key.bagpipe) { + div = 4; + if (s->prv == 0) { + if (s->sym_next == 0 + || s->sym_next->sym_type != ABC_T_NOTE + || !s->sym_next->un.note.grace) + div = 2; + } + } else div = 8; + s->un.note.len /= div; + for (i = 0; i <= s->nhd; i++) + s->un.note.lens[i] /= div; + s->multi = s->stem = 1; + } + if (s->sym_type == ABC_T_NOTE) + s->type = NOTE; + else s->type = REST; + + /* set the note duration */ + s->len = s->un.note.len; + if (s->len >= BASE_LEN) + s->un.note.stemless = 1; + if (grace_head == 0) { + if (curvoice->r_plet == 0) { + if (s->un.note.r_plet == 0) + ; + else if (s->un.note.r_plet > 1) + curvoice->r_plet = s->un.note.r_plet - 1; + else { +/*fixme: should check that in abcparse*/ + alert( "Bad 'r' value in a n-plet sequence (line %d)",s->linenum); + } + } else { /* in a n-plet sequence */ + if (s->un.note.r_plet != 0) { + alert("n-plet sequences on the same notes (line %d)",s->linenum); + } + if (--curvoice->r_plet == 0) + set_nplet(s); + } + } + + memcpy(s->pits, s->un.note.pits, sizeof s->pits); + + /* get the max head type, number of dots and number of flags */ + { + int head, dots, nflags, i, l; + + if ((l = s->un.note.lens[0]) != 0) { + identify_note(s, l, + &head, &dots, &nflags); + s->head = head; + s->dots = dots; + s->nflags = nflags; + } + + for (i = 1; i <= s->nhd; i++) + if (s->un.note.lens[i] != l) + break; + if (i <= s->nhd) { + for (i = 1; i <= s->nhd; i++) { + if (s->un.note.lens[i] == l) + continue; + identify_note(s, s->un.note.lens[i], + &head, &dots, &nflags); + if (head > s->head) + s->head = head; + if (dots > s->dots) + s->dots = dots; + if (nflags > s->nflags) + s->nflags = nflags; + } + } + } + + if (s->un.note.lyric_start) { + lyric_start = s; + lyric_cont = 0; + lyric_nb = 0; + } + + /* convert the decorations */ + if (s->un.note.dc.n > 0) + deco_cnv(&s->un.note.dc, s); + + /* adjust the guitar chords */ + if (s->text != 0) + gchord_adjust(s); +} + +/* -- process a pseudo-comment (%%) -- */ +static struct SYMBOL *process_pscomment(struct SYMBOL *s) +{ + char *p; + char w[32]; + float h1; + + p = s->text + 2; /* skip '%%' */ + if (strncasecmp(p, "fmt ", 4) == 0) + p += 4; /* skip 'fmt' */ + + p = get_str(w, p, sizeof w); + switch (w[0]) { + case 'b': + if (strcmp(w, "begintext") == 0) { + int job; + + if (epsf && s->state != ABC_S_HEAD) + return s; + job = OBEYLINES; + if (*p == '\0' + || strncmp(p, "obeylines", 9) == 0) + ; + else if (strncmp(p, "align", 5) == 0 + || strncmp(p, "justify", 7) == 0) + job = T_JUSTIFY; + else if (strncmp(p, "skip", 4) == 0) + job = SKIP; + else if (strncmp(p, "ragged", 6) == 0 + || strncmp(p, "fill", 4) == 0) + job = T_FILL; + else { + alert("Bad argument for begintext: %s (line %d)", p,s->linenum); + } + output_music(); + for (;;) { + if (s->sym_next == 0) { + alert("EOF found while scanning %%%%begintext (line %d)",s->linenum); + return s; + } + s = s->sym_next; + p = s->text; + if (*p == '%' && p[1] == '%') { + p += 2; + if (strncasecmp(p, "fmt ", 4) == 0) + p += 4; + if (strncmp(p, "endtext", 7) == 0) { + if (job != SKIP) + write_text_block(job, s->state); + return s; + } + } + if (job != SKIP) + add_to_text_block(p, job); + } + /* not reached */ + } + break; + case 'E': + if (strcmp(w, "EPS") == 0) { + float x1, y1, x2, y2; + FILE *epsf2; + char line[BSIZE]; + + output_music(); + if ((epsf2 = fopen(p, "r")) == 0) { + alert("No such file: %s (line %d)", p,s->linenum); + return s; + } + + /* get the bounding box */ + while (fgets(line, sizeof line, epsf2)) { + if (strncmp(line, "%%BoundingBox:", 14) == 0) { + if (sscanf(&line[14], "%f %f %f %f", + &x1, &y1, &x2, &y2) == 4) + break; + } + } + if (strncmp(line, "%%BoundingBox:", 14) != 0) { + alert( "No bounding box in '%s' (line %d)", p,s->linenum); + return s; + } + abskip((y2 - y1) * cfmt.scale); + PUT3("%%start EPS file '%s'\nsave\n" + "/showpage {} def /setpagedevice {pop} def\n" + "%.2f %.2f T\n", + p, -x1, -y1); + rewind(epsf2); + while (fgets(line, sizeof line, epsf2)) /* copy the file */ + PUT1("%s", line); + fclose(epsf2); + PUT0("restore\n%%end EPS\n"); + buffer_eob(); + return s; + } + break; + case 'm': + if (strcmp(w, "multicol") == 0) { + float bposy; + + output_music(); + if (strncmp(p, "start", 5) == 0) { + multicol_max = multicol_start = get_bposy(); + lmarg = cfmt.leftmargin; + rmarg = cfmt.rightmargin; + } else if (strncmp(p, "new", 3) == 0) { + if (multicol_start == 0) + alert("%%%%multicol new without start (line %d)",s->linenum); + else { + bposy = get_bposy(); + if (bposy < multicol_start) + abskip(bposy - multicol_start); + if (bposy < multicol_max) + multicol_max = bposy; + cfmt.leftmargin = lmarg; + cfmt.rightmargin = rmarg; + } + } else if (strncmp(p, "end", 3) == 0) { + if (multicol_start == 0) + alert("%%%%multicol end without start (line %d)",s->linenum); + else { + bposy = get_bposy(); + if (bposy > multicol_max) + abskip(bposy - multicol_max); + cfmt.leftmargin = lmarg; + cfmt.rightmargin = rmarg; + multicol_start = 0; + } + } else { + alert("Unknown keyword '%s' in %%%%multicol (line %d)", p,s->linenum); + } + return s; + } + break; + case 'n': + if (strcmp(w, "newpage") == 0) { + if (epsf) + return s; + output_music(); + write_buffer(); + use_buffer = 0; + if (isdigit(*p)) + pagenum = atoi(p); + write_pagebreak(); + return s; + } + break; + case 's': + if (strcmp(w, "setbarnb") == 0) { + bar_number = atoi(p); + return s; + } + if (strcmp(w, "sep") == 0) { + float h2, len, lwidth; + + output_music(); + lwidth = (cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin; + h1 = h2 = len = 0; + if (*p != '\0') { + h1 = scan_u(p); + while (*p != '\0' && !isspace(*p)) + p++; + while (isspace(*p)) + p++; + } + if (*p != '\0') { + h2 = scan_u(p); + while (*p != '\0' && !isspace(*p)) + p++; + while (isspace(*p)) + p++; + } + if (*p != '\0') + len = scan_u(p); + if (h1 < 1) + h1 = 0.5 * CM; + if (h2 < 1) + h2 = h1; + if (len < 1) + len = 3.0 * CM; + bskip(h1); + PUT2("%.1f %.1f sep0\n", + (lwidth - len) * 0.5 / cfmt.scale, + (lwidth + len) * 0.5 / cfmt.scale); + bskip(h2); + buffer_eob(); + return s; + } + if (strcmp(w, "staffbreak") == 0) { + if (s->state != ABC_S_TUNE + && s->state != ABC_S_EMBED) + return s; + sym_link(s); + s->type = FMTCHG; + s->u = STBRK; + if (*p != '\0') + s->xmx = scan_u(p); + else s->xmx = 0.5 * CM; + return s; + } + if (strcmp(w, "staves") == 0) { + if (s->state == ABC_S_TUNE) { + output_music(); + voice_init(); + } + get_staves(s); + curvoice = first_voice; + staves_found = 1; + return s; + } + break; + case 'c': + case 't': + if (strcmp(w, "text") == 0 || strcmp(w, "center") == 0) { + int job; + + if (epsf && s->state == ABC_S_GLOBAL) + return s; + job = w[0] == 't' ? OBEYLINES : OBEYCENTER; + output_music(); + add_to_text_block(p, job); + write_text_block(job, s->state); + return s; + } + break; + case 'v': + if (strcmp(w, "vskip") == 0) { + output_music(); + h1 = scan_u(p); + if (h1 < 1) + h1 = 0.5 * CM; + bskip(h1); + buffer_eob(); + return s; + } + break; + } + if (s->state == ABC_S_TUNE + || s->state == ABC_S_EMBED) { + if (strcmp(w, "leftmargin") == 0 + || strcmp(w, "rightmargin") == 0 + || strcmp(w, "scale") == 0) { + output_music(); + buffer_eob(); + } + else if (strcmp(w, "postscript") == 0) { + sym_link(s); + s->type = FMTCHG; + s->u = PSSEQ; + return s; + } + } + if (interpret_format_line(w, p) == 0) + ops_into_fmt(); + return s; +} + +/* -- set the duration of notes/rests in a n-plet sequence -- */ +static void set_nplet(struct SYMBOL *s) +{ + struct SYMBOL *t; + int l, r, lplet; + int time; + + l = 0; + for (;;) { + if (s->type == NOTE + || s->type == REST) { + l += s->un.note.len; + if ((r = s->un.note.r_plet) != 0) + break; + } + s = s->prv; + } + lplet = (l * s->un.note.q_plet) / s->un.note.p_plet; + t = s; + time = s->time; + for (;;) { + if (s->type == NOTE + || s->type == REST) { + s->len = (s->un.note.len * lplet) / l; + if (--r == 0) { + s->sflags |= S_NPLET_END; + curvoice->time = time; + break; + } + l -= s->un.note.len; + lplet -= s->len; + time += s->len; + s->sflags |= (S_NPLET_ST|S_NPLET_END); + } + s = s->nxt; + s->time = time; + } + t->sflags &= ~S_NPLET_END; + + /* set the beam break on the last note */ + for (; s != 0; s = s->prv) { + if (s->type == NOTE) { + if (s->un.note.len < QUAVER) + s->sflags |= S_BEAM_BREAK; + break; + } + } +} + +/* -- link a symbol in a voice -- */ +static void sym_link(struct SYMBOL *s) +{ + struct VOICE_S *p_voice = curvoice; + + if (p_voice->sym != 0) { + p_voice->last_symbol->nxt = s; + s->prv = p_voice->last_symbol; + } else p_voice->sym = s; + p_voice->last_symbol = s; + + s->voice = p_voice - voice_tb; + s->staff = p_voice->staff; +} diff --git a/src-abcm2ps/small.fmt b/src-abcm2ps/small.fmt new file mode 100644 index 0000000..004f5a0 --- /dev/null +++ b/src-abcm2ps/small.fmt @@ -0,0 +1,27 @@ +% Very small + + scale 0.38 + titlefont Helvetica-Bold 30 + subtitlefont Times-Bold 20 + composerfont Times-Bold 20 + measurefont Times-Italic 16 + gchordfont Times-Roman 16 + topmargin 0.5cm + botmargin 0.0cm + leftmargin 1.0cm + rightmargin 1.0cm + titlespace 0.0cm + topspace 0.0cm + subtitlespace 0.0cm + composerspace 0.0cm + musicspace 0.0cm + parskipfac 1.0 + staffwidth 18.4cm + staffsep 45 + maxshrink 0.65 + + lineskipfac 1.1 + parskipfac 0 + textspace 0.2cm + textfont Times-Roman 10 + gchordbox 1 diff --git a/src-abcm2ps/subs.cpp b/src-abcm2ps/subs.cpp new file mode 100644 index 0000000..8837ac9 --- /dev/null +++ b/src-abcm2ps/subs.cpp @@ -0,0 +1,816 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include +#include +#include +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +static float twidth; /* text width for %%begintext..%%endtext */ + +/* width of characters according to the encoding */ +/* these are the widths for Times-Roman, extracted from the 'a2ps' package */ +static short ISOLatin1_w[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* \002: hyphen in lyrics */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 250,333,408,500,500,833,778,333, + 333,333,500,564,250,564,250,278, + 500,500,500,500,500,500,500,500, + 500,500,278,278,564,564,564,444, + 921,722,667,667,722,611,556,722, + 722,333,389,722,611,889,722,722, + 556,722,667,556,611,722,722,944, + 722,722,611,333,278,333,469,500, + 333,444,500,444,500,444,333,500, + 500,278,278,500,278,778,500,500, + 500,500,333,389,278,500,500,722, + 500,500,444,480,200,480,541, 0, + 0,500,500,500, 0, 0, 0, 0, /* \201..\203: sharp, flat and natural signs */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 250,333,500,500,500,500,200,500, + 333,760,276,500,564,333,760,333, + 400,564,300,300,333,500,453,350, + 333,278,310,500,750,750,750,444, + 722,722,722,722,722,722,889,667, + 611,611,611,611,333,333,333,333, + 722,722,722,722,722,722,722,564, + 722,722,722,722,722,722,556,500, + 444,444,444,444,444,444,667,444, + 444,444,444,444,278,278,278,278, + 500,500,500,500,500,500,500,564, + 500,500,500,500,500,500,500,500, +}; +static short ISOLatin2_w[96] = { + 250,500,333,611,500,500,500,500, + 333,556,500,500,500,333,611,500, + 400,500,333,278,333,500,500,333, + 333,389,500,500,500,333,444,500, + 500,722,722,500,722,500,500,667, + 500,611,500,611,500,333,333,500, + 500,500,500,722,722,500,722,564, + 500,500,722,500,722,722,500,500, + 500,444,444,500,444,500,500,444, + 500,444,500,444,500,278,278,500, + 500,500,500,500,500,500,500,564, + 500,500,500,500,500,500,500,333, +}; +static short ISOLatin3_w[96] = { + 250,500,333,500,500,500,500,500, + 333,500,500,500,500,333,760,500, + 400,500,300,300,333,500,500,350, + 333,278,500,500,500,750,750,500, + 722,722,722,722,722,500,500,667, + 611,611,611,611,333,333,333,333, + 722,722,722,722,722,500,722,564, + 500,722,722,722,722,500,500,500, + 444,444,444,444,444,500,500,444, + 444,444,444,444,278,278,278,278, + 500,500,500,500,500,500,500,564, + 500,500,500,500,500,500,500,333, +}; +static short ISOLatin4_w[96] = { + 250,500,500,500,500,500,500,500, + 333,556,500,500,500,333,611,333, + 400,500,333,500,333,500,500,333, + 333,389,500,500,500,500,444,500, + 500,722,722,722,722,722,889,500, + 500,611,500,611,500,333,333,500, + 722,500,500,500,722,722,722,564, + 722,500,722,722,722,500,500,500, + 500,444,444,444,444,444,667,500, + 500,444,500,444,500,278,278,500, + 500,500,500,500,500,500,500,564, + 500,500,500,500,500,500,500,333, +}; +static short ISOLatin5_w[96] = { + 250,333,500,500,500,500,200,500, + 333,760,276,500,564,333,760,333, + 400,564,300,300,333,500,453,350, + 333,278,310,500,750,750,750,444, + 722,722,722,722,722,722,889,667, + 611,611,611,611,333,333,333,333, + 500,722,722,722,722,722,722,564, + 722,722,722,722,722,500,500,500, + 444,444,444,444,444,444,667,444, + 444,444,444,444,278,278,278,278, + 500,500,500,500,500,500,500,564, + 500,500,500,500,500,278,500,500, +}; +static short ISOLatin6_w[96] = { + 250,500,500,500,500,500,500,500, + 333,500,556,500,611,333,500,500, + 500,500,500,500,500,500,500,500, + 500,500,389,500,444,500,500,500, + 500,722,722,722,722,722,889,500, + 500,611,500,611,500,333,333,333, + 500,500,500,722,722,722,722,500, + 722,500,722,722,722,722,556,500, + 500,444,444,444,444,444,667,500, + 500,444,500,444,500,278,278,278, + 500,500,500,500,500,500,500,500, + 500,500,500,500,500,500,500,500, +}; + +static short *cw_tb[] = { + ISOLatin1_w, /* 0 = ascii */ + ISOLatin1_w, + ISOLatin2_w - 160, + ISOLatin3_w - 160, + ISOLatin4_w - 160, + ISOLatin5_w - 160, + ISOLatin6_w - 160 +}; + +/* escaped character table */ +/* adapted from the 'recode' package - first index is 128 + 32 */ +static char ISOLatin1_c[] = + "NS!!CtPdCuYeBBSE':Co-a<>141234?I" + "A!A'A>A?A:AAAEC,E!E'E>E:I!I'I>I:D-N?O!O'O>O?O:*XO/U!U'U>U:Y'THss" + "a!a'a>a?a:aaaec,e!e'e>e:i!i'i>i:d-n?o!o'o>o?o:-:o/u!u'u>u:y'thy:"; +static char ISOLatin2_c[] = + "NSA;'(L/CuLA(A:L'C'C,CDO\"O:*XRa(a:l'c'c,cdo\"o:-:rSE':I.S,G(J>-- Z.DGh/2S3S''Myh>.M',i.s,g(j>12 z." + "A!A'A> A:C.C>C,E!E'E>E:I!I'I>I: N?O!O'O>G.O:*XG>U!U'U>U:U(S>ss" + "a!a'a> a:c.c>c,e!e'e>e:i!i'i>i: n?o!o'o>g.o:-:g>u!u'u>u:u(s>'."; +static char ISOLatin4_c[] = + "NSA;kkR,CuI?L,SE':SA?A:AAAEI;CI-D/N,O-K,O>O?O:*XO/U;U'U>U:U?U-ss" + "a-a'a>a?a:aaaei;ci-d/n,o-k,o>o?o:-:o/u;u'u>u:u?u-'."; +static char ISOLatin5_c[] = + "NS!!CtPdCuYeBBSE':Co-a<>141234?I" + "A!A'A>A?A:AAAEC,E!E'E>E:I!I'I>I:G(N?O!O'O>O?O:*XO/U!U'U>U:I.S,ss" + "a!a'a>a?a:aaaec,e!e'e;e:e.i'i>i-g(n?o!o'o>o?o:-:o/u!u'u>u:i.s,y:"; +static char ISOLatin6_c[] = + "NSA;E-G,I-I?K,L,N'R,SA?A:AAAEI;CI:D/N,O-O'O>O?O:U?O/U;U'U>U:Y'THU-" + "a-a'a>a?a:aaaei;ci:d-n,o-o'o>o?o:u?o/u;u'u>u:y'thu-"; +static char *esc_tb[] = { + ISOLatin1_c, /* 0 = ascii */ + ISOLatin1_c, + ISOLatin2_c, + ISOLatin3_c, + ISOLatin4_c, + ISOLatin5_c, + ISOLatin6_c +}; + +static struct text { + struct text *next; + float textw; + char text[2]; +} *text_tb[TEXT_MAX]; + +/* -- print message for internal error and maybe stop -- */ +void bug(const char *msg, int fatal) +{ + alert("This cannot happen! Internal error: %s", msg); + if (fatal) { + alert("Emergency stop"); + exit(3); + } + alert("Trying to continue..."); +} + +/* -- return random float between x1 and x2 -- */ +float ranf(float x1, + float x2) +{ +static int first = 1; + + if (first) { + srand(time(0)); + first = 0; + } + return x1 + (x2 - x1) * (float) (rand() & 0x7fff) / 32768.; +} + +/* -- read a number with a unit -- */ +float scan_u(char *str) +{ + float a; + int nch; + + if (sscanf(str, "%f%n", &a, &nch) == 1) { + if (str[nch] == '\0' || str[nch] == ' ') + return a * PT; + if (!strncasecmp(str + nch, "cm", 2)) + return a * CM; + if (!strncasecmp(str + nch, "in", 2)) + return a * IN; + if (!strncasecmp(str + nch, "pt", 2)) + return a * PT; + } + alert("++++ Unknown unit value '%s'", str); + return 20 * PT; +} + +/* -- capitalize a string -- */ +void cap_str(char *p) +{ + while (*p != '\0') { + *p = toupper(*p); + p++; + } +} + +/* miscellaneous subroutines */ + +/* -- return the character width -- */ +float cwid(char c) +{ + short *w; + w = cw_tb[cfmt.encoding]; + return (float) w[c] / 1000.; +} + +/* -- change string taking care of some tex-style codes -- */ +/* Puts \ in front of ( and ) in case brackets are not balanced, + * interprets all ISOLatin1..6 escape sequences as defined in rfc1345. + * Returns the length of the string as finally given out on paper. + * Also returns an estimate of the string width... */ +void tex_str(char *d, + const char *s, + int maxlen, + float *wid) +{ + float w; + char c1, c2; + char *p_enc, *p; + int i; + + w = 0; + maxlen--; /* have room for EOS */ + p_enc = esc_tb[cfmt.encoding]; + while ((c1 = *s) != '\0') { + switch (c1) { + case '\\': /* backslash sequences */ + s++; + if ((c1 = *s) == '\0') + break; + if (c1 == ' ') + goto addchar1; + if (c1 == '\\' || (c2 = s[1]) == '\0') { + if (--maxlen <= 0) + break; + *d++ = '\\'; + goto addchar1; + } + /* treat escape with octal value */ + if (c1 - '0' <= 3 + && c2 - '0' <= 7 + && s[2] - '0' <= 7) { + if ((maxlen -= 4) <= 0) + break; + *d++ = '\\'; + *d++ = c1; + *d++ = c2; + *d++ = s[2]; + c1 = ((c1 - '0') << 6) + ((c2 - '0') << 3) + s[2] - '0'; + w += cwid(c1); + s += 2; + break; + } + /* convert to rfc1345 */ + switch (c1) { + case '`': c1 = '!'; break; + case '^': c1 = '>'; break; + case '~': c1 = '?'; break; + case '"': c1 = ':'; break; + /* special TeX sequences */ + case 'O': c1 = '/'; c2 = 'O'; s--; break; + case 'o': c1 = '/'; c2 = 'o'; s--; break; + case 'c': if (c2 == 'c' || c2 == 'C') + c1 = ','; + break; + } + switch (c2) { + case '`': c2 = '!'; break; + case '^': c2 = '>'; break; + case '~': c2 = '?'; break; + case '"': c2 = ':'; break; + } + for (i = 32 * 3, p = p_enc; --i >= 0; p += 2) { + if ((*p == c1 && p[1] == c2) + || (*p == c2 && p[1] == c1)) { + s++; + c1 = (p - p_enc) / 2 + 128 + 32; + break; + } + } + goto addchar1; + case '{': + case '}': + break; + case '(': + case ')': /* ( ) becomes \( \) */ + if (--maxlen <= 0) + break; + *d++ = '\\'; + /* fall thru */ + default: /* other characters: pass through */ + addchar1: + if (--maxlen <= 0) + break; + *d++ = c1; + w += cwid(c1); + } + s++; + } + *d = '\0'; + if (wid) + *wid = w; +} + +/* -- output a string in postscript -- */ +static void put_str(const char *str) +{ + char s[801]; + + tex_str(s, str, sizeof s, 0); + PUT1("%s", s); +} + +/* -- output a string in postscript with head and tail -- */ +static void put_str3(const char *head, + const char *str, + const char *tail) +{ + PUT0(head); + put_str(str); + PUT0(tail); +} + +/* -- set font and memorize in case of page break -- */ +static void set_font_init(struct FONTSPEC *font) +{ + set_font(font); + sprintf(page_init, "%.1f F%d ", font->size, font->fnum); +} + +/* -- add_to_text_block -- */ +void add_to_text_block(char *s, + int job) +{ + float lw; + char buf[256]; + + tex_str(buf, s, sizeof buf, &lw); + + /* if first line, set the fonts */ + if (twidth == 0) + set_font_init(&cfmt.textfont); + + /* follow lines */ + if (job == OBEYLINES || job == OBEYCENTER) { + bskip(cfmt.textfont.size * cfmt.lineskipfac); + if (buf[0] != '\0') { + if (job == OBEYLINES) + PUT1("0 0 M (%s) show\n", buf); + else { + PUT2("%.1f 0 M (%s) cshow\n", + ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin) + * 0.5 / cfmt.scale, + buf); + } + } + buffer_eob(); + twidth += lw; + return; + } + + /* fill or justify lines */ + if (twidth == 0) { /* if first line */ + float baseskip; + + baseskip = cfmt.textfont.size * cfmt.lineskipfac; + PUT1("/LF {0 %.1f RM}!\n", -baseskip); + bskip(baseskip); + PUT0("0 0 M ("); + } + PUT1("%s ", buf); + twidth += lw; +} + +/* -- write_text_block -- */ +void write_text_block(int job, + int abc_state) +{ + if (twidth == 0) + return; + + if (job == T_FILL || job == T_JUSTIFY) { + int ntline, nbreak; + float textwidth, ftline, swfac, baseskip; + float lwidth; + + baseskip = cfmt.textfont.size * cfmt.lineskipfac; + lwidth = ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale; + + /* estimate text widths.. ok for T-R, wild guess for other fonts */ + swfac = cfmt.textfont.swfac; + + PUT2(") %.1f P%d\n", + lwidth, job == T_FILL ? 1 : 2); + + /* estimate the skip: + * 1- (total textwidth)/(available width) */ + textwidth = twidth * swfac * cfmt.textfont.size; + ftline = textwidth / lwidth; + /* 2- assume some chars lost at each line end */ + nbreak = (int)ftline; + textwidth += 5 * nbreak * cwid('a') * swfac * cfmt.textfont.size; + ftline = textwidth / lwidth; + ntline = (int)(ftline + 1.0); + bskip((ntline - 1) * baseskip); + } + bskip(cfmt.textfont.size * cfmt.parskipfac); + buffer_eob(); + + /* next line to allow pagebreak after each paragraph */ + if (!epsf && abc_state != ABC_S_TUNE && multicol_start == 0) + write_buffer(); + page_init[0] = '\0'; + + twidth = 0; +} + +/* -- clear_text -- */ +void clear_text(void) +{ + int i; + + for (i = TEXT_MAX; --i >= 0;) { + if (i != TEXT_PS) + text_tb[i] = 0; + } +} + +/* -- add_text -- */ +void add_text(const char *s, + int type) +{ + struct text *t, *r; + + t = (struct text *) malloc(sizeof (struct text) - 2 + + strlen(s) + 1); + strcpy(t->text, s); + t->textw = cwid('a') * strlen(s); + t->next = 0; + if ((r = text_tb[type]) == 0) + text_tb[type] = t; + else { + while (r->next != 0) + r = r->next; + r->next = t; + } +} + +/* -- output a line of words after tune -- */ +static void put_wline(char *p, + float x, + int right) +{ + char *q, *r, sep; + + r = 0; + q = p; + if (isdigit(*p) || p[1] == '.') { + while (*p != '\0') { + p++; + if (*p == ' ' + || p[-1] == ':' + || p[-1] == '.') + break; + } + r = p; + while (*p == ' ') + p++; + } + + /* on the left side, permit page break at empty lines or stanza start */ + if (!right + && (*p == '\0' || r != 0)) + buffer_eob(); + + if (r != 0) { + sep = *r; + *r = '\0'; + PUT1("%.2f 0 M(", x); + put_str(q); + PUT0(")lshow\n"); + *r = sep; + } + if (*p != '\0') { + PUT1("%.2f 0 M(", x + 5); + put_str(p); + PUT0(")show\n"); + } +} + +/* -- put_words -- */ +void put_words(void) +{ + struct text *t, *u, *t_end; + int n, have_text; + float middle, max2col; + + if ((u = text_tb[TEXT_W]) == 0) + return; + + set_font_init(&cfmt.wordsfont); + + /* see if we may have 2 columns */ + middle = 0.5 * ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale; + max2col = (middle - 45.) / (cfmt.wordsfont.swfac * cfmt.wordsfont.size); + n = 0; + have_text = 0; + for (t = u; t != 0; t = t->next) { + if (t->textw > max2col) { + n = 0; + break; + } + if (t->text[0] == '\0') { + if (have_text) { + n++; + have_text = 0; + } + } else have_text = 1; + } + if (n > 0) { + int i; + + n++; + n /= 2; + i = n; + have_text = 0; + for (;;) { + if (u->text[0] == '\0') { + if (have_text + && --i <= 0) + break; + have_text = 0; + } else have_text = 1; + u = u->next; + } + t_end = u; + u = u->next; + } else { + t_end = 0; + u = 0; + } + + /* output the text */ + bskip(cfmt.wordsspace); + for (t = text_tb[TEXT_W]; t != 0 || u != 0;) { + bskip(cfmt.lineskipfac * cfmt.wordsfont.size); + if (t != 0) { + put_wline(t->text, 45., 0); + t = t->next; + if (t == t_end) + t = 0; + } + if (u != 0) { + put_wline(u->text, 20. + middle, 1); + if (u->text[0] == '\0') { + if (--n == 0) { + if (t != 0) + n++; + else if (u->next != 0) { + + /* center the last words */ +/*fixme: should compute the width average.. */ + middle *= 0.6; + } + } + } + u = u->next; + } + } + + buffer_eob(); + page_init[0] = '\0'; +} + +/* -- put_text -- */ +static void put_text(int type, + const char *str) +{ + struct text *t; + char buf[256]; + + if ((t = text_tb[type]) == 0) + return; + + tex_str(buf, t->text, sizeof buf, 0); + bskip(cfmt.textfont.size * cfmt.lineskipfac); + PUT2("0 0 M (%s %s) show\n", str, buf); + while ((t = t->next) != 0) { + bskip(cfmt.textfont.size * cfmt.lineskipfac); + tex_str(buf, t->text, sizeof buf, 0); + PUT1("20 0 M (%s) show\n", buf); + } + bskip(cfmt.textfont.size * cfmt.lineskipfac); + buffer_eob(); +} + +/* -- put_history -- */ +void put_history(void) +{ + struct text *t; + float baseskip, parskip; + + set_font_init(&cfmt.textfont); + baseskip = cfmt.textfont.size * cfmt.lineskipfac; + parskip = cfmt.textfont.size * cfmt.parskipfac; + + bskip(cfmt.textspace); + + if (info.rhyth && !cfmt.infoline) { + bskip(baseskip); + put_str3("0 0 M (Rhythm: ", + info.rhyth, + ") show\n"); + bskip(parskip); + } + + if (info.book) { + bskip(0.5 * CM); + put_str3("0 0 M (Book: ", + info.book, + ") show\n"); + bskip(parskip); + } + + if (info.src) { + bskip(0.5 * CM); + put_str3("0 0 M (Source: ", + info.src, + ") show\n"); + bskip(parskip); + } + + put_text(TEXT_D, "Discography: "); + put_text(TEXT_N, "Notes: "); + put_text(TEXT_Z, "Transcription: "); + + if ((t = text_tb[TEXT_H]) != 0) { + while (t != 0) { + bskip(0.5 * CM); + put_str3("0 0 M (", + t->text, + ") show\n"); + t = t->next; + } + bskip(parskip); + } + buffer_eob(); + page_init[0] = '\0'; +} + +/* -- write a title -- */ +void write_title(int i) +{ + char t[200]; + + if (i == 0) { + bskip(cfmt.titlespace + cfmt.titlefont.size); + set_font(&cfmt.titlefont); + } else { + bskip(cfmt.subtitlespace + cfmt.subtitlefont.size); + set_font(&cfmt.subtitlefont); + } + + strncpy(t, info.title[i], sizeof t - 1); + t[sizeof t - 1] = '\0'; + + if (cfmt.titlecaps) + cap_str(t); + PUT0(" ("); + if (i == 0 && cfmt.withxrefs) + PUT1("%s. ", info.xref); + put_str(t); + if (cfmt.titleleft || i>0) // subtitle always leftside + PUT0(") 0 0 M show\n"); + else PUT1(") %.1f 0 M cshow\n", + 0.5 * ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale); +} + +/* -- write_heading -- */ +void write_heading(void) +{ + float lwidth, down1, down2; + int i, ncl; + const char *rhythm; + + lwidth = ((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth) + - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale; + + /* titles */ + for (i = 0; i < info.ntitle; i++) + write_title(i); + + /* rhythm, composer, origin */ + down1 = cfmt.composerspace + cfmt.composerfont.size; + rhythm = (first_voice->key.bagpipe && !cfmt.infoline) ? info.rhyth : 0; + if (rhythm) { + set_font(&cfmt.composerfont); + PUT2("0 -%.1f M (%s) show\n", + cfmt.composerspace + cfmt.composerfont.size, + info.rhyth); + down1 -= cfmt.composerfont.size; + } + if ((ncl = info.ncomp) > 0 || info.orig) { + set_font(&cfmt.composerfont); + bskip(cfmt.composerspace); + if (ncl == 0) + ncl = 1; + for (i = 0; i < ncl; i++) { + bskip(cfmt.composerfont.size); + PUT1("%.1f 0 M (", lwidth); + if (info.comp[i]) + put_str(info.comp[i]); + if (info.orig && i == ncl - 1) + put_str3(" \\(", + info.orig, + "\\)"); + PUT0(") lshow\n"); + } + down1 += cfmt.composerfont.size * (ncl - 1); + + rhythm = rhythm ? 0 : info.rhyth; + if ((rhythm || info.area) && cfmt.infoline) { + + /* if only one of rhythm or area then do not use ()'s + * otherwise output 'rhythm (area)' */ + set_font(&cfmt.infofont); + bskip(cfmt.infofont.size + cfmt.infospace); + PUT1("%.1f 0 M (", lwidth); + if (rhythm) { + PUT1("%s", rhythm); + if (info.area) + PUT1(" \\(%s\\)", info.area); + } else PUT1("%s", info.area); + PUT0(") lshow\n"); + down1 += cfmt.infofont.size + cfmt.infospace; + } + down2 = 0; + } else { + down2 = cfmt.composerspace + cfmt.composerfont.size; + } + + /* parts */ + if (info.parts && cfmt.printparts) { + down1 = cfmt.partsspace + cfmt.partsfont.size - down1; + if (down1 > 0) + down2 += down1; + if (down2 > 0.01) + bskip(down2); + PUT2("%.1f F%d ", cfmt.partsfont.size, cfmt.partsfont.fnum); + put_str3("0 0 M (", info.parts, ") show\n"); + down2 = 0; + } + bskip(down2 + cfmt.musicspace); +} + +/* -- output the user defined postscript sequences -- */ +void write_user_ps(void) +{ + struct text *t; + + t = text_tb[TEXT_PS]; + while (t != 0) { + fprintf(fout, "%s\n", t->text); + t = t->next; + } +} diff --git a/src-abcm2ps/syms.cpp b/src-abcm2ps/syms.cpp new file mode 100644 index 0000000..1ed1c1c --- /dev/null +++ b/src-abcm2ps/syms.cpp @@ -0,0 +1,947 @@ +/* + * abcm2ps: a program to typeset tunes written in abc format using PostScript + * Adapted from abcm2ps: + * Copyright (C) 1998-2003 Jean-Fran�ois Moine + * Adapted from abc2ps-1.2.5: + * Copyright (C) 1996,1997 Michael Methfessel + * + * 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 2 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. +*/ + +#include +#include + +#include "abcparse.h" +#include "abc2ps.h" + +/* subroutines to define postscript macros which draw symbols */ + +static char ps_head[] = + "/xymove{2 copy /y exch def /x exch def M}!\n" + + /* str cshow - center at current pt */ + "/cshow{dup stringwidth pop 2 div neg 0 RM show}!\n" + + /* str lshow - show left-aligned */ + "/lshow{dup stringwidth pop neg 0 RM show}!\n" + + /* str showb - show in box */ + "/showb{ dup currentpoint 3 -1 roll show\n" + " 0.6 setlinewidth\n" + " exch 2 sub exch 3 sub 3 -1 roll\n" + " stringwidth pop 4 add fh 4 add rectstroke}!\n" + + /* l x y wln - write ligne */ + "/wln{M 0.8 setlinewidth 0 RL stroke}!\n" + + "/whf{3 add 3 3 1 roll wln}!\n" + + /* x y tclef - treble clef */ + "/tclef{ M\n" + " -1.9 3.7 RM\n" + " -3.3 1.9 -3.1 6.8 2.4 8.6 RC\n" + " 7.0 0.0 9.8 -8.0 4.1 -11.7 RC\n" + " -5.2 -2.4 -12.5 0.0 -13.3 6.2 RC\n" + " -0.7 6.4 4.15 10.5 10.0 15.3 RC\n" + " 4.0 4.0 3.6 6.1 2.8 9.6 RC\n" + " -2.3 -1.5 -4.7 -4.8 -4.5 -8.5 RC\n" + " 0.8 -12.2 3.4 -17.3 3.5 -26.3 RC\n" + " 0.3 -4.4 -1.2 -6.2 -3.8 -6.2 RC\n" + " -3.7 -0.1 -5.8 4.3 -2.8 6.1 RC\n" + " 3.9 1.9 6.1 -4.6 1.4 -4.8 RC\n" + " 0.7 -1.2 4.6 -0.8 4.2 4.2 RC\n" + " -0.2 10.3 -3.0 15.7 -3.5 28.3 RC\n" + " 0.0 4.1 0.6 7.4 5.0 10.6 RC\n" + " 2.3 -3.2 2.9 -10.0 1.0 -12.7 RC\n" + " -2.4 -4.3 -11.5 -10.3 -11.8 -15.0 RC\n" + " 0.4 -7.0 6.9 -8.5 11.7 -6.1 RC\n" + " 3.9 3.0 1.3 8.8 -3.7 8.1 RC\n" + " -4.0 -0.2 -4.8 -3.1 -2.7 -5.7 RC\n" + " fill}!\n" + + "/stclef{exch 0.85 div exch 0.85 div gsave 0.85 dup scale tclef grestore}!\n" + + /* x y octu - upper '8' */ + "/octu{/Times-Roman 12 selectfont M -1.5 34 RM (8) show}!\n" + /* x y octl - lower '8' */ + "/octl{/Times-Roman 12 selectfont M -3.5 -19 RM (8) show}!\n" + + /* x y bclef - bass clef */ + "/bclef{ M\n" + " -8.8 3.5 RM\n" + " 6.3 1.9 10.2 5.6 10.5 10.8 RC\n" + " 0.3 4.9 -0.5 8.1 -2.6 8.8 RC\n" + " -2.5 1.2 -5.8 -0.7 -5.9 -4.1 RC\n" + " 1.8 3.1 6.1 -0.6 3.1 -3.0 RC\n" + " -3.0 -1.4 -5.7 2.3 -1.9 7.0 RC\n" + " 2.6 2.3 11.4 0.6 10.1 -8.0 RC\n" + " -0.1 -4.6 -5.0 -10.2 -13.3 -11.5 RC\n" + " 15.5 17.0 RM\n" + " 0.0 1.5 2.0 1.5 2.0 0.0 RC\n" + " 0.0 -1.5 -2.0 -1.5 -2.0 0.0 RC\n" + " 0.0 -5.5 RM\n" + " 0.0 1.5 2.0 1.5 2.0 0.0 RC\n" + " 0.0 -1.5 -2.0 -1.5 -2.0 0.0 RC\n" + " fill}!\n" + + "/sbclef{exch 0.85 div exch 0.85 div gsave 0.85 dup scale 0 3 T bclef grestore}!\n" + + "/cchalf{0 0 M 0.0 12.0 RM\n" + " 2.6 5.0 RL\n" + " 2.3 -5.8 5.2 -2.4 4.7 1.6 RC\n" + " 0.4 3.9 -3.0 6.7 -5.1 4.0 RC\n" + " 4.1 0.5 0.9 -5.3 -0.9 -1.4 RC\n" + " -0.5 3.4 6.5 4.3 7.8 -0.8 RC\n" + " 1.9 -5.6 -4.1 -9.8 -6.0 -5.4 RC\n" + " -1.6 -3.0 RL\n" + " fill}!\n" + + /* x y cclef */ + "/cclef{ gsave T\n" + " cchalf 0 24 T 1 -1 scale cchalf\n" + " -5.0 0 M 0 24 RL 3 0 RL 0 -24 RL fill\n" + " -0.5 0 M 0 24 RL 0.8 setlinewidth stroke grestore}!\n" + + "/scclef{exch 0.85 div exch 0.85 div gsave 0.85 dup scale\n" + " 0 2 T cclef grestore}!\n" + + /* x y pclef */ + "/pclef{ M 1.4 setlinewidth -2.7 2 RM\n" + " 0 20 RL 5.4 0 RL 0 -20 RL -5.4 0 RL stroke}!\n" + "/spclef{pclef}!\n" + + /* t dx dy x y bm - beam, depth t */ + "/bm{ M 3 copy RL neg 0 exch RL\n" + " neg exch neg exch RL 0 exch RL fill}!\n" + + /* str x y bnum - number on beam */ + "/bnum{M /Times-Italic 12 selectfont cshow}!\n" + + /* x1 y1 x2 y2 hbr - half bracket */ + "/hbr{M dlw lineto 0 -3 RL stroke}!\n" + + /* x y r00 - longa rest */ + "/r00{ xymove\n" + " -1 6 RM 0 -12 RL 3 0 RL 0 12 RL fill}!\n" + + /* x y r0 - breve rest */ + "/r0{ xymove\n" + " -1 6 RM 0 -6 RL 3 0 RL 0 6 RL fill}!\n" + + /* x y r1 - rest */ + "/r1{ xymove\n" + " -3 6 RM 0 -3 RL 7 0 RL 0 3 RL fill}!\n" + + /* x y r2 - half rest */ + "/r2{ xymove\n" + " -3 0 RM 0 3 RL 7 0 RL 0 -3 RL fill}!\n" + + /* x y r4 - quarter rest */ + "/r4{ xymove\n" + " -0.5 8.9 RM\n" + " 1.3 -3.4 RL\n" + " -2.0 -4.5 RL\n" + " 3.1 -4.8 RL\n" + " -3.2 3.5 -5.8 -1.4 -1.4 -3.8 RC\n" + " -1.9 2.0 -0.8 5.0 2.4 2.6 RC\n" + " -2.2 4.2 RL\n" + " 0.0 0.0 2.0 4.7 2.1 4.7 RC\n" + " -3.3 5.0 RL\n" + " fill}!\n" + + /* 1/8 .. 1/64 rest element */ + "/r8e{ -1.5 -1.5 -2.4 -2.0 -3.6 -2.0 RC\n" + " 2.4 2.8 -2.8 4.0 -2.8 1.2 RC\n" + " 0.0 -2.7 4.3 -2.4 5.9 -0.6 RC\n" + " fill}!\n" + + /* x y r8 - eighth rest */ + "/r8{ xymove\n" + " 0.5 setlinewidth 3.3 4.0 RM\n" + " -3.4 -9.6 RL stroke\n" + " x y M 3.4 4.0 RM r8e}!\n" + + /* x y r16 - 16th rest */ + "/r16{ xymove\n" + " 0.5 setlinewidth 3.3 4.0 RM\n" + " -4.0 -15.6 RL stroke\n" + " x y M 3.4 4.0 RM r8e\n" + " x y M 1.9 -2.0 RM r8e}!\n" + + /* x y r32 - 32th rest */ + "/r32{ xymove\n" + " 0.5 setlinewidth 4.8 10.0 RM\n" + " -5.5 -21.6 RL stroke\n" + " x y M 4.9 10.0 RM r8e\n" + " x y M 3.4 4.0 RM r8e\n" + " x y M 1.9 -2.0 RM r8e}!\n" + + /* x y r64 - 64th rest */ + "/r64{ xymove\n" + " 0.5 setlinewidth 4.8 10.0 RM\n" + " -7.0 -27.6 RL stroke\n" + " x y M 4.9 10.0 RM r8e\n" + " x y M 3.4 4.0 RM r8e\n" + " x y M 1.9 -2.0 RM r8e\n" + " x y M 0.3 -8.0 RM r8e}!\n" + + /* dx dy dt - dot shifted by dx,dy */ + "/dt{y add exch x add exch M currentpoint 1.2 0 360 arc fill}!\n" + + /* x y hld - fermata */ + "/hld{ 1.5 add 2 copy 1.5 add M currentpoint 1.3 0 360 arc\n" + " M -7.5 0 RM\n" + " 0 11.5 15 11.5 15 0 RC\n" + " -0.25 0 RL\n" + " -1.25 9 -13.25 9 -14.50 0 RC\n" + " fill}!\n" + + /* x y dnb - down bow */ + "/dnb{ dlw M\n" + " -3.2 2.0 RM\n" + " 0.0 7.2 RL\n" + " 6.4 0.0 RM\n" + " 0.0 -7.2 RL\n" + " currentpoint stroke M\n" + " -6.4 4.8 RM\n" + " 0.0 2.4 RL\n" + " 6.4 0.0 RL\n" + " 0.0 -2.4 RL\n" + " fill}!\n" + + /* x y upb - up bow */ + "/upb{ dlw M -2.6 9.4 RM\n" + " 2.6 -8.8 RL\n" + " 2.6 8.8 RL\n" + " stroke}!\n" + + /* x y grm - gracing mark */ + "/grm{ M -5 2.5 RM\n" + " 5.0 8.5 5.5 -4.5 10.0 2.0 RC\n" + " -5.0 -8.5 -5.5 4.5 -10.0 -2.0 RC fill}!\n" + + /* x y stc - staccato mark */ + "/stc{M currentpoint 1.2 0 360 arc fill}!\n" + + /* x y emb - emphasis bar */ + "/emb{ 1.2 setlinewidth 1 setlinecap M\n" + " -2.5 0 RM 5 0 RL stroke 0 setlinecap}!\n" + + /* x y cpu - roll sign above head */ + "/cpu{ M -6 0 RM\n" + " 0.4 7.3 11.3 7.3 11.7 0 RC\n" + " -1.3 6 -10.4 6 -11.7 0 RC fill}!\n" + + /* x y sld - slide */ + "/sld{ M -7.2 -4.8 RM\n" + " 1.8 -0.7 4.5 0.2 7.2 4.8 RC\n" + " -2.1 -5.0 -5.4 -6.8 -7.6 -6.0 RC fill}!\n" + + /* x y trl - trill sign */ + "/trl{ /Times-BoldItalic 16 selectfont\n" + " M -4 2 RM (tr) show}!\n" + + /* x y umrd - upper mordent */ + "/umrd{ 4 add M\n" + " 2.2 2.2 RL 2.1 -2.9 RL 0.7 0.7 RL\n" + " -2.2 -2.2 RL -2.1 2.9 RL -0.7 -0.7 RL\n" + " -2.2 -2.2 RL -2.1 2.9 RL -0.7 -0.7 RL\n" + " 2.2 2.2 RL 2.1 -2.9 RL 0.7 0.7 RL fill}!\n" + + /* x y lmrd - lower mordent */ + "/lmrd{ 2 copy umrd 8 add M\n" + " 0.6 setlinewidth 0 -8 RL stroke}!\n" + + /* str x y fng - finger (0-5) */ + "/fng{/Bookman-Demi 8 selectfont M -3 1 RM show}!\n" + + /* str x y dacs - D.C. / D.S. */ + "/dacs{/Times-Roman 16 selectfont 3 add M cshow}!\n" + + /* x y brth - breath */ + "/brth{/Times-BoldItalic 30 selectfont 6 add M (,) show}!\n" + + /* str x y pf - p, f, pp, .. */ + "/pf{/Times-BoldItalic 16 selectfont 5 add M cshow}!\n" + + /* str x y sfz */ + "/sfz{ exch 4 sub exch 5 add M pop\n" + " /Times-Italic 14 selectfont (s) show\n" + " /Times-BoldItalic 16 selectfont (f) show\n" + " /Times-Italic 14 selectfont (z) show}!\n" + + /* x y coda - coda */ + "/coda{ 1 setlinewidth 2 add 2 copy M 0 20 RL\n" + " 2 copy 10 add exch -10 add exch M 20 0 RL stroke\n" + " 10 add 6 0 360 arc 1.7 setlinewidth stroke}!\n" + + /* x y sgno - segno */ + "/sgno{ M 0 3 RM currentpoint currentpoint currentpoint\n" + " 1.5 -1.7 6.4 0.3 3.0 3.7 RC\n" + " -10.4 7.8 -8.0 10.6 -6.5 11.9 RC\n" + " 4.0 1.9 5.9 -1.7 4.2 -2.6 RC\n" + " -1.3 -0.7 -2.9 1.3 -0.7 2.0 RC\n" + " -1.5 1.7 -6.4 -0.3 -3.0 -3.7 RC\n" + " 10.4 -7.8 8.0 -10.6 6.5 -11.9 RC\n" + " -4.0 -1.9 -5.9 1.7 -4.2 2.6 RC\n" + " 1.3 0.7 2.9 -1.3 0.7 -2.0 RC\n" + " fill\n" + " M 0.8 setlinewidth -6.0 1.2 RM 12.6 12.6 RL stroke\n" + " 7 add exch -6 add exch 1.2 0 360 arc fill\n" + " 8 add exch 6 add exch 1.2 0 360 arc fill}!\n" + + /* w x y cresc - (de)crescendo */ + "/cresc{ 1.2 setlinewidth 6 add M\n" + " dup 4 RL neg 4 RL stroke}!\n" + + /* x y dplus - + decoration */ + "/dplus{ 1.2 setlinewidth M 0 0.5 RM 0 6 RL\n" + " -3 -3 RM 6 0 RL stroke}!\n" + + /* x y accent - accent */ + "/accent{1.2 setlinewidth M -4 2 RM\n" + " 8 2 RL -8 2 RL stroke}!\n" + + /* x y turn - turn */ + "/turn{ M 5.2 8 RM\n" + " 1.4 -0.5 0.9 -4.8 -2.2 -2.8 RC\n" + " -4.8 3.5 RL\n" + " -3.0 2.0 -5.8 -1.8 -3.6 -4.4 RC\n" + " 1.0 -1.1 2.0 -0.8 2.1 0.1 RC\n" + " 0.1 0.9 -0.7 1.2 -1.9 0.6 RC\n" + " -1.4 0.5 -0.9 4.8 2.2 2.8 RC\n" + " 4.8 -3.5 RL\n" + " 3.0 -2.0 5.8 1.8 3.6 4.4 RC\n" + " -1.0 1.1 -2 0.8 -2.1 -0.1 RC\n" + " -0.1 -0.9 0.7 -1.2 1.9 -0.6 RC\n" + " fill}!\n" + + /* x y trnx - turn with line through it */ + "/turnx{ 2 copy turn M\n" + " 0.6 setlinewidth 0 1.5 RM 0 9 RL stroke}!\n" + + /* x y lphr - longphrase */ + "/lphr{1.2 setlinewidth M 0 -18 RL stroke}!\n" + + /* x y mphr - mediumphrase */ + "/mphr{1.2 setlinewidth M 0 -12 RL stroke}!\n" + + /* x y sphr - shortphrase */ + "/sphr{1.2 setlinewidth M 0 -6 RL stroke}!\n" + + /* len x y ltr - long trill */ + "/ltr{ gsave 4 add T\n" + " 0 6 3 -1 roll{\n" + /* % first loop draws left half of squiggle; second draws right\n*/ + " 0 1 1{\n" + " 0.0 0.4 M\n" + " 2.0 1.9 3.4 2.3 3.9 0.0 curveto\n" + " 2.1 0.0 lineto\n" + " 1.9 0.8 1.4 0.7 0.0 -0.4 curveto\n" + " fill\n" + " pop 180 rotate -6 0 translate\n" + " } for\n" + /* % shift axes right one squiggle*/ + " pop 6 0 translate\n" + " } for\n" + " grestore}!\n" + + /* len x ylow arp - arpeggio */ + "/arp{gsave 90 rotate exch neg ltr grestore}!\n" + + /* x y wedge - wedge */ + "/wedge{1 add M -1.5 5 RL 3 0 RL -1.5 -5 RL fill}!\n" + + /* x y opend - 'open' sign */ + "/opend{dlw M currentpoint 3 add 2.5 -90 270 arc stroke}!\n" + + /* x y snap - 'snap' sign */ + "/snap{ dlw M currentpoint -3 6 RM\n" + " 0 5 6 5 6 0 RC\n" + " 0 -5 -6 -5 -6 0 RC\n" + " 5 add M 0 -6 RL stroke}!\n" + + /* x y thumb - 'thumb' sign */ + "/thumb{ dlw M currentpoint -2.5 7 RM\n" + " 0 6 5 6 5 0 RC\n" + " 0 -6 -5 -6 -5 0 RC\n" + " 2 add M 0 -4 RL stroke}!\n" + + /* y hl - helper line at height y */ + "/hl{ 0.8 setlinewidth x -6.5 add exch M\n" + " 13 0 RL stroke}!\n" + + /* y hl1 - longer helper line */ + "/hl1{ 0.8 setlinewidth x -8 add exch M\n" + " 16 0 RL stroke}!\n" + + /* -- accidentals -- */ + /* x y sh0 - sharp sign */ + "/sh0{ gsave T 0.9 setlinewidth\n" + " -1.2 -8.4 M 0 15.4 RL\n" + " 1.4 -7.2 M 0 15.4 RL stroke\n" + " -2.6 -3 M 5.4 1.6 RL 0 -2.2 RL -5.4 -1.6 RL 0 2.2 RL fill\n" + " -2.6 3.4 M 5.4 1.6 RL 0 -2.2 RL -5.4 -1.6 RL 0 2.2 RL fill\n" + " grestore}!\n" + /* dx sh - sharp relative to head */ + "/sh{x add y sh0}!\n" + /* x y ft0 - flat sign */ + "/ft0{ gsave T 0.8 setlinewidth\n" + " -1.8 2.5 M\n" + " 6.4 3.3 6.5 -3.6 0 -6.6 RC\n" + " 4.6 3.9 4.5 7.6 0 5.7 RC\n" + " currentpoint fill M\n" + " 0 7.1 RM 0 -12.6 RL stroke\n" + " grestore}!\n" + /* dx ft - flat relative to head */ + "/ft{x add y ft0}!\n" + /* x y nt0 - natural sign */ + "/nt0{ gsave T 0.5 setlinewidth\n" + " -2 -4.3 M 0 12.2 RL\n" + " 1.3 -7.8 M 0 12.2 RL stroke\n" + " 2.1 setlinewidth\n" + " -2 -2.9 M 3.3 0.6 RL\n" + " -2 2.4 M 3.3 0.6 RL stroke\n" + " grestore}!\n" + /* dx nt - natural relative to head */ + "/nt{x add y nt0}!\n" + /* x y ftx - narrow flat sign */ + "/ftx{ M -1.4 2.7 RM\n" + " 5.7 3.1 5.7 -3.6 0.0 -6.7 RC\n" + " 3.9 4.0 4.0 7.6 0.0 5.8 RC\n" + " currentpoint fill M\n" + " dlw 0 7.1 RM 0 -12.4 RL stroke}!\n" + /* x y dft0 ft - double flat sign */ + "/dft0{2 copy exch 2.5 sub exch ftx exch 1.5 add exch ftx}!\n" + /* dx dft - double flat relative to head */ + "/dft{x add y dft0}!\n" + /* x y dsh0 - double sharp */ + "/dsh0{ 2 copy M 0.7 setlinewidth\n" + " -2 -2 RM 4 4 RL\n" + " -4 0 RM 4 -4 RL stroke\n" + " 0.5 setlinewidth 2 copy M 1.3 -1.3 RM\n" + " 2 -0.2 RL 0.2 -2 RL -2 0.2 RL -0.2 2 RL fill\n" + " 2 copy M 1.3 1.3 RM\n" + " 2 0.2 RL 0.2 2 RL -2 -0.2 RL -0.2 -2 RL fill\n" + " 2 copy M -1.3 1.3 RM\n" + " -2 0.2 RL -0.2 2 RL 2 -0.2 RL 0.2 -2 RL fill\n" + " M -1.3 -1.3 RM\n" + " -2 -0.2 RL -0.2 -2 RL 2 0.2 RL 0.2 2 RL fill}!\n" + /* dx dsh - double sharp relative to head */ + "/dsh{x add y dsh0}!\n" + + /* accidentals in guitar chord */ + "/tempstr 1 string def\n" + "/sharp_glyph{\n" + " fh 0.4 mul 0 RM currentpoint\n" + " gsave T fh 0.08 mul dup scale 0 7 sh0 grestore\n" + " fh 0.4 mul 0 RM}!\n" + "/flat_glyph{\n" + " fh 0.4 mul 0 RM currentpoint\n" + " gsave T fh 0.08 mul dup scale 0 5 ft0 grestore\n" + " fh 0.4 mul 0 RM}!\n" + "/nat_glyph{\n" + " fh 0.4 mul 0 RM currentpoint\n" + " gsave T fh 0.08 mul dup scale 0 7 nt0 grestore\n" + " fh 0.4 mul 0 RM}!\n" + /* str gcshow - guitar chord */ + "/gcshow{\n" + " {dup 129 eq {sharp_glyph}\n" + " {dup 130 eq {flat_glyph}\n" + " {dup 131 eq {nat_glyph}\n" + " {tempstr 0 2 index put tempstr show}\n" + " ifelse}\n" + " ifelse}\n" + " ifelse pop}\n" + " forall}!\n" + /* x y w h box - draw a box */ + "/box{0.6 setlinewidth" + " rectstroke}!\n" + + /* h x y bar - thin bar */ + "/bar{M dlw 0 exch RL stroke}!\n" + + /* h x y dbar - dashed bar */ + "/dabar{[5] 0 setdash bar [] 0 setdash}!\n" + + /* h x y thbar - thick bar */ + "/thbar{M dup 0 exch RL 3 0 RL 0 exch neg RL fill}!\n" + + /* x y rdots - repeat dots */ + "/rdots{ 9 add M currentpoint 2 copy 1.2 0 360 arc\n" + " 6 add M currentpoint 1.2 0 360 arc fill}!\n" + + /* x y csig - C timesig */ + "/csig{ M\n" + " 1.0 17.3 RM\n" + " 0.9 -0.0 2.3 -0.7 2.4 -2.2 RC\n" + " -1.2 2.0 -3.6 -0.1 -1.6 -1.7 RC\n" + " 2.0 -1.0 3.8 3.5 -0.8 4.7 RC\n" + " -2.0 0.4 -6.4 -1.3 -5.8 -7.0 RC\n" + " 0.4 -6.4 7.9 -6.8 9.1 -0.7 RC\n" + " -2.3 -5.6 -6.7 -5.1 -6.8 0.0 RC\n" + " -0.5 4.4 0.7 7.5 3.5 6.9 RC\n" + " fill}!\n" + + /* x y ctsig - C| timesig */ + "/ctsig{dlw 2 copy csig 4 add M 0 16 RL stroke}!\n" + + /* (top) (bot) x y tsig - time signature */ + "/tsig{ M gsave /Times-Bold 16 selectfont 1.2 1 scale\n" + " 0 1 RM currentpoint 3 -1 roll cshow\n" + " M 0 12 RM cshow grestore}!\n" + + /* (meter) x y stsig - single time signature */ + "/stsig{ M gsave /Times-Bold 18 selectfont 1.2 1 scale\n" + " 0 6 RM cshow grestore}!\n" + + /* l x y staff - 5 lines staff */ + "/staff{ M dlw dup 0 RL dup neg 6 RM\n" + " dup 0 RL dup neg 6 RM\n" + " dup 0 RL dup neg 6 RM\n" + " dup 0 RL dup neg 6 RM\n" + " 0 RL stroke}!\n" + + /* x1 x2 sep0 - hline separator */ + "/sep0{dlw 0 M 0 lineto stroke}!\n" + + "/hbrce{ -2.5 1.0 RM\n" + " -4.5 -4.6 -7.5 -12.2 -4.4 -26.8 RC\n" + " 3.5 -14.3 3.2 -21.7 -2.1 -24.2 RC\n" + " 7.4 2.4 7.3 14.2 3.5 29.5 RC\n" + " -2.7 9.5 -1.5 16.2 3.0 21.5 RC\n" + " fill}!\n" + /* h x y brace */ + "/brace{ gsave T 0 0 M 0.01 mul 1.0 exch scale hbrce\n" + " 0 -100 M 1 -1 scale hbrce grestore}!\n" + + /* h x y bracket */ + "/bracket{M dlw -5 2 RM currentpoint\n" + " -1.7 2 RM 10.5 -1 12 4.5 12 3.5 RC\n" + " 0 -1 -3.5 -5.5 -8.5 -5.5 RC fill\n" + " 3 setlinewidth M 0 2 RM\n" + " 0 exch neg -8 add RL currentpoint stroke\n" + " dlw M -1.7 0 RM\n" + " 10.5 1 12 -4.5 12 -3.5 RC\n" + " 0 1 -3.5 5.5 -8.5 5.5 RC fill}!\n" + + /* nb_measures x y mrest */ + "/mrest{ gsave T 1 setlinewidth\n" + " -20 6 M 0 12 RL 20 6 M 0 12 RL stroke\n" + " 3 setlinewidth -20 12 M 40 0 RL stroke\n" + // " 5 setlinewidth -20 12 M 40 0 RL stroke\n" + " /Times-Bold 15 selectfont 0 28 M cshow grestore}!\n" + + /* x y mrep - measure repeat */ + "/mrep{ 2 copy 2 copy\n" + " M -5 16 RM currentpoint 1.4 0 360 arc\n" + " M 5 8 RM currentpoint 1.4 0 360 arc\n" + " M -7 6 RM 11 12 RL 3 0 RL -11 -12 RL -3 0 RL\n" + " fill}!\n" + + /* x y mrep2 - measure repeat 2 times */ + "/mrep2{ 2 copy 2 copy\n" + " M -5 18 RM currentpoint 1.4 0 360 arc\n" + " M 5 6 RM currentpoint 1.4 0 360 arc fill\n" + " M 1.8 setlinewidth\n" + " -7 4 RM 14 10 RL -14 -4 RM 14 10 RL\n" + " stroke}!\n" + + /* str bracket_type dx x y repbra - repeat bracket */ + "/repbra{gsave dlw T 0 -20 M\n" + " 0 20 3 index 1 ne {RL} {RM} ifelse 0 RL 0 ne {0 -20 RL} if stroke\n" + " 4 -13 M show grestore}!\n" + + /* pp2x pp1x p1 pp1 pp2 p2 p1 SL */ + "/SL{M curveto RL curveto fill}!\n" + + /* -- text -- */ + "/dsp{dup stringwidth pop}!\n" + "/glue{ 2 copy length exch length add string dup 4 2 roll 2 index 0 3 index\n" + " putinterval exch length exch putinterval}!\n" + "/TXT{/txt exch def}!\n" + "/rejoin{( ) search pop exch glue}!\n" + "/measure{dsp txt stringwidth pop add textwidth 2 add gt}!\n" + "/join{txt exch glue TXT}!\n" + "/find{search {pop 3 -1 roll 1 add 3 1 roll}{pop exit} ifelse}!\n" + "/spacecount{0 exch ( ) {find} loop}!\n" + "/jproc{dsp textwidth exch sub exch dup spacecount}!\n" + "/popzero{dup 0 eq {pop}{div} ifelse}!\n" + "/justify{jproc 1 sub 3 2 roll exch popzero 0 32 4 3 roll widthshow} def\n" + + /* str lwidth P1 */ + "/P1{ /textwidth exch def () TXT\n" + " dup spacecount{\n" + " rejoin measure {gsave txt show grestore LF () TXT join}{join} ifelse\n" + " } repeat gsave txt show grestore LF () TXT pop}!\n" + + /* str lwidth P2 */ + "/P2{ /textwidth exch def () TXT\n" + " dup spacecount{\n" + " rejoin measure {gsave txt justify grestore LF () TXT join}{join} ifelse\n" + " } repeat gsave txt show grestore LF () TXT pop}!\n" + + /* x y hd - full head */ + "/hd{ xymove\n" + " 3.5 2.0 RM\n" + " -2.0 3.5 -9.0 -0.5 -7.0 -4.0 RC\n" + " 2.0 -3.5 9.0 0.5 7.0 4.0 RC fill}!\n" + + /* x y Hd - open head for half */ + "/Hd{ xymove\n" + " 3.0 1.6 RM\n" + " -1.0 1.8 -7.0 -1.4 -6.0 -3.2 RC\n" + " 1.0 -1.8 7.0 1.4 6.0 3.2 RC\n" + " 0.5 0.3 RM\n" + " 2.0 -3.8 -5.0 -7.6 -7.0 -3.8 RC\n" + " -2.0 3.8 5.0 7.6 7.0 3.8 RC\n" + " fill}!\n" + + /* x y HD - open head for whole */ + "/HD{ xymove\n" + " -1.6 2.4 RM\n" + " 2.8 1.6 6.0 -3.2 3.2 -4.8 RC\n" + " -2.8 -1.6 -6.0 3.2 -3.2 4.8 RC\n" + " 7.2 -2.4 RM\n" + " 0.0 1.8 -2.2 3.2 -5.6 3.2 RC\n" + " -3.4 0.0 -5.6 -1.4 -5.6 -3.2 RC\n" + " 0.0 -1.8 2.2 -3.2 5.6 -3.2 RC\n" + " 3.4 0.0 5.6 1.4 5.6 3.2 RC\n" + " fill}!\n" + + /* x y HDD - round breve */ + "/HDD{ dlw HD\n" + " x y M -6 -4 RM 0 8 RL\n" + " x y M 6 -4 RM 0 8 RL stroke}!\n" + + /* x y breve - square breve */ + "/breve{ xymove\n" + " 2.5 setlinewidth -6 -2.7 RM 12 0 RL\n" + " 0 5.4 RM -12 0 RL stroke\n" + " dlw x y M -6 -5 RM 0 10 RL\n" + " x y M 6 -5 RM 0 10 RL stroke}!\n" + + /* x y longa */ + "/longa{ xymove\n" + " 2.5 setlinewidth -6 -2.7 RM 12 0 RL\n" + " 0 5.4 RM -12 0 RL stroke\n" + " dlw x y M -6 -5 RM 0 10 RL\n" + " x y M 6 -10 RM 0 15 RL stroke}!\n" + + /* tin whistle */ + "/tw_head{/Helvetica 8.0 selectfont\n" + " 0 -45 M 90 rotate (WHISTLE) show -90 rotate\n" + " /Helvetica-Bold 36.0 selectfont\n" + " 0 -45 M show .5 setlinewidth newpath}!\n" + "/tw_under{\n" + " 1 index 2.5 sub -4 M 2.5 -2.5 RL 2.5 2.5 RL\n" + " -2.5 -2.5 RM 0 6 RL stroke}!\n" + "/tw_over{\n" + " 1 index 2.5 sub -3 M 2.5 2.5 RL 2.5 -2.5 RL\n" + " -2.5 2.5 RM 0 -6 RL stroke}!\n" + "/tw_0{7 sub 2 copy 3.5 sub 3 0 360 arc stroke}!\n" + "/tw_1{7 sub 2 copy 3.5 sub 2 copy 3 90 270 arc fill 3 270 90 arc stroke}!\n" + "/tw_2{7 sub 2 copy 3.5 sub 3 0 360 arc fill}!\n" + "/tw_p{pop -55 M 0 6 RL -3 -3 RM 6 0 RL stroke}!\n" + "/tw_pp{ pop 3 sub -53.5 M 6 0 RL\n" + " -1.5 -1.5 RM 0 3 RL\n" + " -3 0 RM 0 -3 RL stroke}!\n"; + +static const char *enc_tb[MAXENC] = { + /* 1 */ + "/space /exclamdown /cent /sterling /currency /yen /brokenbar /section\n" + "/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron\n" +/* "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /bullet\n" */ + "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /periodcentered\n" +/* "/cedilla /dotlessi /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n" */ + "/cedilla /onesuperior /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n" + /* (300) */ + "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n" + "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n" + "/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply\n" + "/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /germandbls\n" + "/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n" + "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n" + "/eth /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide\n" + "/oslash /ugrave /uacute /ucircumflex /udieresis /yacute /thorn /ydieresis", + /* 2 */ + "/space /Aogonek /breve /Lslash /currency /Lcaron /Sacute /section\n" + "/dieresis /Scaron /Scedilla /Tcaron /Zacute /hyphen /Zcaron /Zdotaccent\n" + "/degree /aogonek /ogonek /lslash /acute /lcaron /sacute /caron\n" + "/cedilla /scaron /scedilla /tcaron /zacute /hungarumlaut /zcaron /zdotaccent\n" + /* (300) */ + "/Racute /Aacute /Acircumflex /Abreve /Adieresis /Lacute /Cacute /Ccedilla\n" + "/Ccaron /Eacute /Eogonek /Edieresis /Ecaron /Iacute /Icircumflex /Dcaron\n" + "/Dbar /Nacute /Ncaron /Oacute /Ocircumflex /Ohungarumlaut /Odieresis /multiply\n" + "/Rcaron /Uring /Uacute /Uhungarumlaut /Udieresis /Yacute /Tcedilla /germandbls\n" + "/racute /aacute /acircumflex /abreve /adieresis /lacute /cacute /ccedilla\n" + "/ccaron /eacute /eogonek /edieresis /ecaron /iacute /icircumflex /dcaron\n" + "/dbar /nacute /ncaron /oacute /ocircumflex /ohungarumlaut /odieresis /divide\n" + "/rcaron /uring /uacute /uhungarumlaut /udieresis /yacute /tcedilla /dotaccent", + /* 3 */ + "/space /Hstroke /breve /sterling /currency /yen /Hcircumflex /section\n" + "/dieresis /Idotaccent /Scedilla /Gbreve /Jcircumflex /hyphen /registered /Zdotaccent\n" + "/degree /hstroke /twosuperior /threesuperior /acute /mu /hcircumflex /bullet\n" + "/cedilla /dotlessi /scedilla /gbreve /jcircumflex /onehalf /threequarters /zdotaccent\n" + /* (300) */ + "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Cdotaccent /Ccircumflex /Ccedilla\n" + "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n" + "/Eth /Ntilde /Ograve /Oacute /Ocircumflex /Gdotaccent /Odieresis /multiply\n" + "/Gcircumflex /Ugrave /Uacute /Ucircumflex /Udieresis /Ubreve /Scircumflex /germandbls\n" + "/agrave /aacute /acircumflex /atilde /adieresis /cdotaccent /ccircumflex /ccedilla\n" + "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n" + "/eth /ntilde /ograve /oacute /ocircumflex /gdotaccent /odieresis /divide\n" + "/gcircumflex /ugrave /uacute /ucircumflex /udieresis /ubreve /scircumflex /dotaccent", + /* 4 */ + "/space /Aogonek /kra /Rcedilla /currency /Itilde /Lcedilla /section\n" + "/dieresis /Scaron /Emacron /Gcedilla /Tbar /hyphen /Zcaron /macron\n" + "/degree /aogonek /ogonek /rcedilla /acute /itilde /lcedilla /caron\n" + "/cedilla /scaron /emacron /gcedilla /tbar /Eng /zcaron /eng\n" + /* (300) */ + "/Amacron /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Iogonek\n" + "/Ccaron /Eacute /Eogonek /Edieresis /Edotaccent /Iacute /Icircumflex /Imacron\n" + "/Eth /Ncedilla /Omacron /Kcedilla /Ocircumflex /Otilde /Odieresis /multiply\n" + "/Oslash /Uogonek /Uacute /Ucircumflex /Udieresis /Utilde /Umacron /germandbls\n" + "/amacron /aacute /acircumflex /atilde /adieresis /aring /ae /iogonek\n" + "/ccaron /eacute /eogonek /edieresis /edotaccent /iacute /icircumflex /imacron\n" + "/dbar /ncedilla /omacron /kcedilla /ocircumflex /otilde /odieresis /divide\n" + "/oslash /uogonek /uacute /ucircumflex /udieresis /utilde /umacron /dotaccent", + /* 5 */ + "/space /exclamdown /cent /sterling /currency /yen /brokenbar /section\n" + "/dieresis /copyright /ordfeminine /guillemotleft /logicalnot /hyphen /registered /macron\n" + "/degree /plusminus /twosuperior /threesuperior /acute /mu /paragraph /bullet\n" + "/cedilla /dotlessi /ordmasculine /guillemotright /onequarter /onehalf /threequarters /questiondown\n" + /* (300) */ + "/Agrave /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Ccedilla\n" + "/Egrave /Eacute /Ecircumflex /Edieresis /Igrave /Iacute /Icircumflex /Idieresis\n" + "/Gbreve /Ntilde /Ograve /Oacute /Ocircumflex /Otilde /Odieresis /multiply\n" + "/Oslash /Ugrave /Uacute /Ucircumflex /Udieresis /Idotaccent /Scedilla /germandbls\n" + "/agrave /aacute /acircumflex /atilde /adieresis /aring /ae /ccedilla\n" + "/egrave /eacute /ecircumflex /edieresis /igrave /iacute /icircumflex /idieresis\n" + "/gbreve /ntilde /ograve /oacute /ocircumflex /otilde /odieresis /divide\n" + "/oslash /ugrave /uacute /ucircumflex /udieresis /dotlessi /scedilla /ydieresis", + /* 6 */ + "/space /Aogonek /Emacron /Gcedilla /Imacron /Itilde /Kcedilla /Lcedilla\n" + "/acute /Rcedilla /Scaron /Tbar /Zcaron /hyphen /kra /Eng\n" + "/dbar /aogonek /emacron /gcedilla /imacron /itilde /kcedilla /lcedilla\n" + "/nacute /rcedilla /scaron /tbar /zcaron /section /germandbls /eng\n" + /* (300) */ + "/Amacron /Aacute /Acircumflex /Atilde /Adieresis /Aring /AE /Iogonek\n" + "/Ccaron /Eacute /Eogonek /Edieresis /Edotaccent /Iacute /Icircumflex /Idieresis\n" + "/Dbar /Ncedilla /Omacron /Oacute /Ocircumflex /Otilde /Odieresis /Utilde\n" + "/Oslash /Uogonek /Uacute /Ucircumflex /Udieresis /Yacute /Thorn /Umacron\n" + "/amacron /aacute /acircumflex /atilde /adieresis /aring /ae /iogonek\n" + "/ccaron /eacute /eogonek /edieresis /edotaccent /iacute /icircumflex /idieresis\n" + "/eth /ncedilla /omacron /oacute /ocircumflex /otilde /odieresis /utilde\n" + "/oslash /uogonek /uacute /ucircumflex /udieresis /yacute /thorn /umacron" +}; + +/* -- define which latin encoding -- */ +void define_encoding(int enc) /* Latin encoding number */ +{ + if (enc <= 0) + return; + fprintf(fout, + "/ISOLatin%dEncoding [\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/space /exclam /quotedbl /numbersign /dollar /percent /ampersand /quoteright\n" + "/parenleft /parenright /asterisk /plus /comma /minus /period /slash\n" + "/zero /one /two /three /four /five /six /seven\n" + "/eight /nine /colon /semicolon /less /equal /greater /question\n" + /* (100) */ + "/at /A /B /C /D /E /F /G\n" + "/H /I /J /K /L /M /N /O\n" + "/P /Q /R /S /T /U /V /W\n" + "/X /Y /Z /bracketleft /backslash /bracketright /asciicircum /underscore\n" + "/quoteleft /a /b /c /d /e /f /g\n" + "/h /i /j /k /l /m /n /o\n" + "/p /q /r /s /t /u /v /w\n" + "/x /y /z /braceleft /bar /braceright /asciitilde /.notdef\n" + /* (200) */ + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef /.notdef\n" + "/dotlessi /grave /acute /circumflex /tilde /macron /breve /dotaccent\n" + "/dieresis /.notdef /ring /cedilla /.notdef /hungarumlaut /ogonek /caron\n" + "%s\n" + "] def\n", + enc, enc_tb[enc - 1]); +} + +/* -- define_font -- */ +void define_font(char name[], + int num) +{ + if (strcmp(name, "Symbol") == 0) { + fprintf(fout, "/F%d{dup 0.8 mul /fh exch def" + " /%s exch selectfont}!\n", + num, name); + return; + } + fprintf(fout, "/%s-ISO /%s mkfontext\n" + "/F%d{dup 0.8 mul /fh exch def /%s-ISO exch selectfont}!\n", + name, name, num, name); +} + +/* -- def_stems -- */ +static void def_stems(FILE *fp) +{ + /* len su - up stem */ + fprintf(fp, "/su{dlw x y M %.1f %.1f RM %.1f sub 0 exch RL stroke}!\n", + STEM_XOFF, STEM_YOFF, STEM_YOFF); + + /* len sd - down stem */ + fprintf(fp, "/sd{dlw x y M %.1f %.1f RM neg %.1f add 0 exch RL stroke}!\n", + -STEM_XOFF, -STEM_YOFF, STEM_YOFF); +} + +/* -- stem and flags -- */ +static void def_flags(FILE *fp) +{ + /* n len sfu - stem and n flag up */ + fprintf(fp, "/sfu{ dlw x y M %.1f %.1f RM\n" + " %.1f sub 0 exch RL currentpoint stroke\n" + " M dup 1 eq\n" + " {\n" + " pop\n" + " 0.6 -5.6 9.6 -9.0 5.6 -18.4 RC\n" + " 1.6 6.0 -1.3 11.6 -5.6 12.8 RC\n" + " fill\n" + " }{\n" + " 2 1 3 -1 roll {\n" + " pop currentpoint\n" + " 0.9 -3.7 9.1 -6.4 6.0 -12.4 RC\n" + " 1.0 5.4 -4.2 8.4 -6.0 8.4 RC\n" + " fill -5.4 add M\n" + " } for\n" + " 1.2 -3.2 9.6 -5.7 5.6 -14.6 RC\n" + " 1.6 5.4 -1.0 10.2 -5.6 11.4 RC\n" + " fill\n" + " }\n" + " ifelse}!\n", + STEM_XOFF, STEM_YOFF, STEM_YOFF); + + /* n len sfd - stem and n flag down */ + fprintf(fp, "/sfd{ dlw x y M -%.1f -%.1f RM\n" + " neg %.1f add 0 exch RL currentpoint stroke\n" + " M dup 1 eq\n" + " {\n" + " pop\n" + " 0.6 5.6 9.6 9.0 5.6 18.4 RC\n" + " 1.6 -6.0 -1.3 -11.6 -5.6 -12.8 RC\n" + " fill\n" + " }{\n" + " 2 1 3 -1 roll {\n" + " pop currentpoint\n" + " 0.9 3.7 9.1 6.4 6.0 12.4 RC\n" + " 1.0 -5.4 -4.2 -8.4 -6.0 -8.4 RC\n" + " fill 5.4 add M\n" + " } for\n" + " 1.2 3.2 9.6 5.7 5.6 14.6 RC\n" + " 1.6 -5.4 -1.0 -10.2 -5.6 -11.4 RC\n" + " fill\n" + " }\n" + " ifelse}!\n", + STEM_XOFF, STEM_YOFF, STEM_YOFF); + + /* n len sfs - stem and n straight flag down */ + fprintf(fp, "/sfs{ dlw x y M -%.1f -%.1f RM\n" + " neg %.1f add 0 exch RL currentpoint stroke\n" + " M 1 1 3 -1 roll {\n" + " pop currentpoint\n" + " 7 %.1f RL\n" + " 0 %.1f RL\n" + " -7 -%.1f RL\n" + " fill 5.4 add M\n" + " } for}!\n", + STEM_XOFF, STEM_YOFF, STEM_YOFF, + BEAM_DEPTH, BEAM_DEPTH, BEAM_DEPTH); +} + +/* -- grace notes -- */ +static void def_gnote(FILE *fp) +{ + /* x y ghd - grace note head */ + fprintf(fp, "/ghd{ xymove\n" + " -1.3 1.5 RM\n" + " 2.4 2 5 -1 2.6 -3 RC\n" + " -2.4 -2 -5 1 -2.6 3 RC fill}!\n" + + /* l gu - grace note stem */ + "/gu{ 0.6 setlinewidth x y M\n" + " %.1f 0 RM 0 exch RL stroke}!\n", + GSTEM_XOFF); + + /* n len sgu - gnote stem and n flag up */ + fprintf(fp, "/sgu{ 0.6 setlinewidth x y M %.1f 0 RM\n" + " 0 exch RL currentpoint stroke\n" + " M dup 1 eq\n" + " {\n" + " pop\n" + " 0.6 -3.4 5.6 -3.8 3.0 -10.0 RC\n" + " 1.2 4.4 -1.4 7.0 -3.0 7.0 RC\n" + " fill\n" + " }{\n" + " 1 1 3 -1 roll {\n" + " pop currentpoint\n" + " 1.0 -3.2 5.6 -2.8 3.2 -8.0 RC\n" + " 1.4 4.8 -2.4 5.4 -3.2 5.2 RC\n" + " fill -3.5 add M\n" + " } for\n" + " }\n" + " ifelse}!\n", + GSTEM_XOFF); + + /* n len sgs - gnote stem and n straight flag up */ + fprintf(fp, "/sgs{ 0.6 setlinewidth x y M %.1f 0 RM\n" + " 0 exch RL currentpoint stroke\n" + " M 1 1 3 -1 roll {\n" + " pop currentpoint\n" + " 3 -1.5 RL 0 -2 RL -3 1.5 RL\n" + " closepath fill -3 add M\n" + " } for}!\n", + GSTEM_XOFF); + + /* ga - acciaccatura */ + fprintf(fp, + "/ga{x y M -1 4 RM 9 5 RL stroke}!\n" + + /* x y ghl - grace note helper line */ + "/ghl{ 0.6 setlinewidth x -3 add exch M\n" + " 6 0 RL stroke}!\n" + + /* x1 y2 x2 y2 x3 y3 x0 y0 gsl */ + "/gsl{dlw M curveto stroke}!\n" + + /* -- grace note accidentals -- */ + "/gsc{gsave x add y T 0.7 dup scale 0 0}!\n" + /* x y gsh */ + "/gsh{gsc sh0 grestore}!\n" + /* x y gnt */ + "/gnt{gsc nt0 grestore}!\n" + /* x y gft */ + "/gft{gsc ft0 grestore}!\n" + /* x y gdsh */ + "/gdsh{gsc dsh0 grestore}!\n" + /* x y gdft */ + "/gdft{gsc dft0 grestore}!\n"); +} + +/* -- define_symbols: write postscript macros to file -- */ +void define_symbols(void) +{ + fputs(ps_head, fout); + def_stems(fout); + def_flags(fout); + def_gnote(fout); +} diff --git a/src-tr-sco/Makefile b/src-tr-sco/Makefile new file mode 100644 index 0000000..f9d02cc --- /dev/null +++ b/src-tr-sco/Makefile @@ -0,0 +1,12 @@ +# Using env variable DBG, see ../src/Makefile +CC = g++ +ADIR=../src +OBJS=$(ADIR)/str.o + +.SUFFIXES= + +tr-sco: $(OBJS) tr-sco.cpp + $(CC) -O $(DBG) -o tr-sco -I$(ADIR) tr-sco.cpp $(OBJS) + +$(ADIR)/%.o: $(ADIR)/%.cpp $(ADIR)/%.h + make -C $(ADIR) $(@F) diff --git a/src-tr-sco/tr-sco.cpp b/src-tr-sco/tr-sco.cpp new file mode 100644 index 0000000..f8f1d96 --- /dev/null +++ b/src-tr-sco/tr-sco.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include + +#include +typedef unsigned int uint; + +enum { // from amuc.cpp + eScore_start, // saving and restoring from file + eScore_end, // not used + ePlayNote=2, + eGlobal=4, + eScore_start4=7, +}; +const uint eHi=1,eLo=2; + +static char task; +static int st_shift, + signs_mode=eLo; +static const int sclin_max=45; // from amuc.h + +void alert(const char *form,...) { + char buf[100]; + va_list ap; + va_start(ap,form); + vsnprintf(buf,100,form,ap); + va_end(ap); + puts(buf); +} + +int lnr_to_midinr(int lnr,uint sign) { // ScLine -> midi note number + int ind=lnr%7; + // b a g f e d c + static const int ar[7]={ 0,2,4,6,7,9,11 }; + int nr = ar[ind] + (sign==eHi ? -1 : sign==eLo ? 1 : 0) + (lnr-ind)/7*12; + // middle C: amuc: lnr=27, ind=6, nr=11+21/7*12=47 + // midi: 60 + return 107-nr; // 60=107-47 + // lnr=0, sign=0 -> 107 + // lnr=sclin_max=45 -> 29 +} + +bool midinr_to_lnr(int mnr,int& lnr,int& sign) { + static const int + // c cis d es e f fis g gis a bes b + ar_0[12]={ 0 , 0 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 6 , 6 }, + s_0[12]= { 0 ,eHi, 0 ,eLo, 0 , 0 ,eHi, 0 ,eHi, 0 ,eLo, 0 }, + + // c des d es e f ges g as a bes b + ar_f[12]={ 0 , 1 , 1 , 2 , 2 , 3 , 4 , 4 , 5 , 5 , 6 , 6 }, + s_f[12]= { 0 ,eLo, 0 ,eLo, 0 , 0 ,eLo, 0 ,eLo, 0 ,eLo, 0 }, + + // c cis d dis e f fis g gis a ais b + ar_s[12]={ 0 , 0 , 1 , 1 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 }, + s_s[12]= { 0 ,eHi, 0 ,eHi, 0 , 0 ,eHi, 0 ,eHi, 0 ,eHi, 0 }; + int ind=mnr%12, + lnr2; + const int *ar, *s; + + switch (signs_mode) { + case eLo: ar=ar_f; s=s_f; break; + case eHi: ar=ar_s; s=s_s; break; + case 0: ar=ar_0; s=s_0; break; + default: + ar=ar_0; s=s_0; + alert("midinr_to_lnr: signs_mode = %d",signs_mode); + } + + // middle C: amuc: lnr=27 + // midi: 60, 60/12*7 = 35, 27=62-35 + + lnr2=62 - mnr/12*7 - ar[ind]; + if (lnr2<0 || lnr2>=sclin_max) return false; + lnr=lnr2; + sign=s[ind]; + return true; +} + +bool transform(FILE *in,FILE *out,char mode) { + int res; + uint opc=0; + Str save_buf; + char textbuf[100]; + for (;;) { + save_buf.rword(in," \n"); + if (!save_buf.s[0]) break; + opc=atoi(save_buf.s); + switch (opc) { + case eGlobal: { + int meter; + if (sscanf(save_buf.s,"%*um%d",&meter)!=1) { + alert("bad code: %s",save_buf.s); return false; + } + fprintf(out,"%um%d",eGlobal,mode=='h' ? meter/2 : mode=='d' ? meter*2 : meter); + } + break; + case eScore_start4: { + int sc_meter,end_sect,ngroup,sc_key_nr; + res=sscanf(save_buf.s,"%*um%de%dg%dk%d", + &sc_meter, + &end_sect,&ngroup,&sc_key_nr); + if (res!=4) { + alert("bad code: %s",save_buf.s); + return false; + }; + if (fscanf(in," \"%50[^\"]s",textbuf)!=1) { alert("missing text"); return false; } + textbuf[50]=0; + getc(in); + switch (mode) { + case 'h': end_sect/=2; break; + case 'd': end_sect*=2; break; + case 's': sc_key_nr=0; break; + } + fprintf(out,"\n%um%de%dg%dk%d ",eScore_start4,sc_meter,end_sect,ngroup,sc_key_nr); + fprintf(out,"\"%s\" ",textbuf); + } + break; + case ePlayNote: { + int lnr,snr,dur,sign,stacc_sampl,col,gnr,dlnr=0,dsnr=0,del_s=0,del_e=0; + res=sscanf(save_buf.s,"%*uL%dN%dd%di%ds%dc%dg%dp%d,%dD%d,%d", + &lnr,&snr,&dur,&sign,&stacc_sampl,&col,&gnr,&dlnr,&dsnr,&del_s,&del_e); + if (res<7 || res>11) { + alert("bad code: %s",save_buf.s); return false; + } + const char *fstring="%uL%dN%dd%di%ds%dc%dg%d "; + int sampled= (stacc_sampl>>1) & 1; + if (mode=='h') { + if (sampled) + fprintf(out,fstring,ePlayNote,lnr,snr/2,dur,sign,stacc_sampl,col,gnr); + else if (dur>1) + fprintf(out,fstring,ePlayNote,lnr,snr/2,dur/2,sign,stacc_sampl,col,gnr); + } + else if (mode=='d') { + if (sampled) + fprintf(out,fstring,ePlayNote,lnr,snr*2,dur,sign,stacc_sampl,col,gnr); + else + fprintf(out,fstring,ePlayNote,lnr,snr*2,dur*2,sign,stacc_sampl,col,gnr); + } + else if (mode=='q') { + if (sampled) + fprintf(out,fstring,ePlayNote,lnr,snr,dur,sign,stacc_sampl,col,gnr); + else + fprintf(out,fstring,ePlayNote,lnr,snr,dur,sign,stacc_sampl,col,gnr); + } + else if (mode=='s') { + if (sampled) + fprintf(out,fstring,ePlayNote,lnr,snr,dur,sign,stacc_sampl,col,gnr); + else { + int midinr=lnr_to_midinr(lnr,sign); + bool ok=midinr_to_lnr(midinr+st_shift,lnr,sign); + if (ok) + fprintf(out,fstring,ePlayNote,lnr,snr,dur,sign,stacc_sampl,col,gnr); + } + } + else { alert("transform: mode %c?",mode); exit(1); } + } + break; + default: alert("tr-sco: unknown opcode %d",opc); return false; + } + } + return true; +} + +void usage() { + puts("Usage:"); + puts(" tr-sco "); + puts("Options:"); + puts(" -trh : write file tr.sco, notes halved"); + puts(" -trd : write file tr.sco, notes doubled"); + puts(" -trq : write file tr.sco, notes quantitized, delays omitted"); + puts(" -trs : write file tr.sco, notes shifted n semi-tones (accidentals will be flat's)"); + puts(" -hi : with -trs, accidentals will be sharp's"); + puts("Modified score file written to tr.sco"); +} + +int main(int argc,char **argv) { + char *inf=0; + for (int an=1;an"); + exit(1); + } + if (task=='s') { + if (++an==argc) { alert("number after -trs missing"); exit(1); } + st_shift=atoi(argv[an]); + } + } + else if (!strcmp(argv[an],"-hi")) + signs_mode=eHi; + else inf=argv[an]; + } + if (!inf) { alert("input file missing"); exit(1); } + if (!task) { alert("what must be done?"); exit(1); } + if (!strcmp(inf,"tr.sco")) { alert("please rename input file tr.sco"); exit(1); } + + FILE *in_tr=fopen(inf,"r"); + if (!in_tr) { alert("Input file %s could not be opened",inf); exit(1); } + FILE *out_tr=fopen("tr.sco","w"); + if (!out_tr) { alert("Output file tr.sco could not be opened"); exit(1); } + + if (!transform(in_tr,out_tr,task)) exit(1); + puts("Output file: tr.sco"); +} diff --git a/src-wav2score/Makefile b/src-wav2score/Makefile new file mode 100644 index 0000000..4a61ab4 --- /dev/null +++ b/src-wav2score/Makefile @@ -0,0 +1,22 @@ +include ../Makefile.inc +CC=g++ +ADIR=../src +OBJS=$(ADIR)/x-widgets.o $(ADIR)/snd-interface.o $(ADIR)/str.o wav2score.o fft.o +OPT=-O -Wuninitialized -Wno-multichar + +.SUFFIXES= + +wav2score: $(OBJS) + $(CC) $(OBJS) -o wav2score $(LDFLAGS) + +%.o: %.cpp + $(CC) -c -I../src $(OPT) $(CFLAGS) $< + +$(ADIR)/x-widgets.o: $(ADIR)/x-widgets.cpp $(ADIR)/x-widgets.h + @cd $(ADIR); make x-widgets.o + +$(ADIR)/alsa-interface.o: $(ADIR)/alsa-interface.cpp $(ADIR)/snd-interface.h + @cd $(ADIR); make alsa-interface.o + +wav2score.o: fft.h $(ADIR)/x-widgets.h $(ADIR)/snd-interface.h +fft.o: fft.h diff --git a/src-wav2score/fft.cpp b/src-wav2score/fft.cpp new file mode 100644 index 0000000..c12f2d5 --- /dev/null +++ b/src-wav2score/fft.cpp @@ -0,0 +1,166 @@ +/* +** Copyright (C) 2000 George Tzanetakis +** Modified by W.Boeke for wav2score - wave-to-score translator. +** +** 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 2 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, write to the Free Software +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* +System for calculating the magnitude of the FFT (Fast Fourier +Transform) of the input fvec. The actual fft code is scammed from +the CARL software and is very similar to the fft implementation in +Numerical Recipes. +*/ + +#include +#include "fft.h" + +/* + * bitreverse places float array x containing N/2 complex values + * into bit-reversed order + */ + +void bitreverse(float x[], int N ) +{ + float rtemp, itemp ; + int i, j, m ; + for ( i = j = 0 ; i < N ; i += 2, j += m ) { + if ( j > i ) { + rtemp = x[j] ; itemp = x[j+1] ; /* complex exchange */ + x[j] = x[i] ; x[j+1] = x[i+1] ; + x[i] = rtemp ; x[i+1] = itemp ; + } + for ( m = N>>1 ; m >= 2 && j >= m ; m >>= 1 ) + j -= m ; + } +} + +void cfft(float x[], int NC, bool forward ); + +/* + * If forward is true, rfft replaces 2*N real data points in x with + * N complex values representing the positive frequency half of their + * Fourier spectrum, with x[1] replaced with the real part of the Nyquist + * frequency value. If forward is false, rfft expects x to contain a + * positive frequency spectrum arranged as before, and replaces it with + * 2*N real values. N MUST be a power of 2. + */ +void rfft( float x[], int N, bool forward ) +{ + float c1, c2, h1r, h1i, h2r, h2i, wr, wi, wpr, wpi, temp, theta ; + float xr, xi ; + int i, i1, i2, i3, i4, N2p1 ; + static int first = 1 ; + theta = M_PI/N ; + wr = 1. ; + wi = 0. ; + c1 = 0.5 ; + if ( forward ) { + c2 = -0.5 ; + cfft( x, N, forward ) ; + xr = x[0] ; + xi = x[1] ; + } + else + { + c2 = 0.5 ; + theta = -theta ; + xr = x[1] ; + xi = 0. ; + x[1] = 0. ; + } + wpr = -2.*pow( sin( 0.5*theta ), 2. ) ; + wpi = sin( theta ) ; + N2p1 = N*2 + 1 ; + for ( i = 0 ; i <= N>>1 ; i++ ) + { + i1 = i*2 ; + i2 = i1 + 1 ; + i3 = N2p1 - i2 ; + i4 = i3 + 1 ; + if ( i == 0 ) { + h1r = c1*(x[i1] + xr ) ; + h1i = c1*(x[i2] - xi ) ; + h2r = -c2*(x[i2] + xi ) ; + h2i = c2*(x[i1] - xr ) ; + x[i1] = h1r + wr*h2r - wi*h2i ; + x[i2] = h1i + wr*h2i + wi*h2r ; + xr = h1r - wr*h2r + wi*h2i ; + xi = -h1i + wr*h2i + wi*h2r ; + } + else { + h1r = c1*(x[i1] + x[i3] ) ; + h1i = c1*(x[i2] - x[i4] ) ; + h2r = -c2*(x[i2] + x[i4] ) ; + h2i = c2*(x[i1] - x[i3] ) ; + x[i1] = h1r + wr*h2r - wi*h2i ; + x[i2] = h1i + wr*h2i + wi*h2r ; + x[i3] = h1r - wr*h2r + wi*h2i ; + x[i4] = -h1i + wr*h2i + wi*h2r ; + } + wr = (temp = wr)*wpr - wi*wpi + wr ; + wi = wi*wpr + temp*wpi + wi ; + } + if ( forward ) + x[1] = xr ; + else + cfft( x, N, forward ) ; +} + +/* + * cfft replaces float array x containing NC complex values + * (2*NC float values alternating real, imagininary, etc.) + * by its Fourier transform if forward is true, or by its + * inverse Fourier transform if forward is false, using a + * recursive Fast Fourier transform method due to Danielson + * and Lanczos. NC MUST be a power of 2. + */ +void cfft(float x[], int NC, bool forward ) +{ + float wr, wi, wpr, wpi, theta, scale ; + int mmax, ND, m, i, j, delta ; + ND = NC*2 ; + bitreverse( x, ND ) ; + for ( mmax = 2 ; mmax < ND ; mmax = delta ) { + delta = mmax*2 ; + theta = 2*M_PI/( forward? mmax : -mmax ) ; + wpr = -2.*pow( sin( 0.5*theta ), 2. ) ; + wpi = sin( theta ) ; + wr = 1. ; + wi = 0. ; + for ( m = 0 ; m < mmax ; m += 2 ) { + register float rtemp, itemp ; + for ( i = m ; i < ND ; i += delta ) + { + j = i + mmax ; + rtemp = wr*x[j] - wi*x[j+1] ; + itemp = wr*x[j+1] + wi*x[j] ; + x[j] = x[i] - rtemp ; + x[j+1] = x[i+1] - itemp ; + x[i] += rtemp ; + x[i+1] += itemp ; + } + wr = (rtemp = wr)*wpr - wi*wpi + wr ; + wi = wi*wpr + rtemp*wpi + wi ; + } + } + /* + * scale output + */ + scale = forward ? 1./ND : 2. ; + float *xi=x, *xe=x+ND ; + while ( xi < xe ) + *xi++ *= scale ; +} diff --git a/src-wav2score/fft.h b/src-wav2score/fft.h new file mode 100644 index 0000000..e99444c --- /dev/null +++ b/src-wav2score/fft.h @@ -0,0 +1 @@ +void rfft( float x[], int N, bool forward ); diff --git a/src-wav2score/wav2score.cpp b/src-wav2score/wav2score.cpp new file mode 100644 index 0000000..072aa68 --- /dev/null +++ b/src-wav2score/wav2score.cpp @@ -0,0 +1,1470 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fft.h" + +typedef unsigned int uint; +typedef unsigned char uchar; + +const int + spect_size=800, // spectrum window + ww_size=800, // wave window + fftw_size=696, // fft-sample window + total_width=806, + peaks_max=3, // spectrum peaks + sclin_dist=3, // score line distance + midi_min=30, midi_max=110, // displayed score notes + notes_max=40, // displayed notes + note_len=5; // displayed note length + +WinBase *top_win; + +bool debug, + options; + +char *inf=0; // wave file +Str configf; // configuration file + +enum { + eSelect1=1, + eSelect2, + eUnselect1, + eUnselect2 +}; + +static uint cGreen,cLightYellow,cFreqScale,cWaveBgr,cLightGrey,cWave; + +const char *wav2s_icon[]={ // made with kiconedit +"16 16 3 1", +"# c #000000", +"a c #ff0000", +". c #ffffff", +"....##......aa..", +"...#..#...aaaa..", +"..#...#.aaaaaa..", +"..#...aaaaaa.a..", +".#...aaaaa...a..", +".#...aaa.....a..", +"#....a.#.....a..", +"#....a.#.....a..", +"#....a..#....a.#", +".....a..#.aaaa.#", +".....a..#aaaaa.#", +".....a..#aaaaa#.", +"..aaaa..#.aaa.#.", +".aaaaa...#...#..", +".aaaaa...#..#...", +"..aaa.....##...."}; + +template +struct Arr { + T *buf; + uint dim; + T nul; + Arr():dim(0) { } + void init(uint d) { dim=d; buf=new T[dim]; } + T& operator[](uint ind) { + if (ind +struct FixedArr { + T buf[dim]; + T& operator[](uint ind) { + if (ind spectlines; // buffer for spectrum lines, lines are 2 or 4 pixels apart + SpectData(): + base_freq(1.), + maxmag(0), + lst_sline(-1), + midinr(0), + nr_peaks(-1), + skipped(false), + bf_valid(false), + asf_valid(false) { } +}; + +struct ScoreView { + uint svwin; + SubWin *subw; + ScoreView(Point); +}; + +struct Wav2Score { + int called, // eval() called + data_size, // nr bytes in wav file + nr_fft_samples, + nr_wav_samples, + buf_size, // fft buffer size + wwh, // wave window height + the_xpos1, // selection in wave window + the_xpos2, + mult_spl, // multiplier spectrum line + min_midinr, + max_midinr, + start, stop; // freq range + bool wave_init, + stereo, + use_each_sample, + i_am_playing, + stop_requested; + char select_chan; // 0, 'L' or 'R' + float *hamming, + mid_C, // midi nr for middle-C, default 48 + spect_thresh, // spectrum-line threshold + dur_inc, // note duration increase, default 1.0 + mult; // freq scaling + Arr spectdata; + short *shortbuf; // contents of the wav file + struct WavLin { + char yhi1,ylo1, + yhi2,ylo2; + } *wavelines; + ScoreView *sv_win; + Wav2Score(): + buf_size(0), + min_midinr(0), + max_midinr(0), + use_each_sample(false), + select_chan(0), + mid_C(48.0), + spect_thresh(0.2), + dur_inc(1.), + sv_win(0) { + } + void init(); + bool read_wav_head(const char *inf,FILE*& wav); + bool read_wav(FILE *wav,short *fb1,short *fb2,const int bufsize); + void eval(float *buffer1,float *buffer2); + void eval_wavf(FILE* wav); + void eval_wavf_gui(FILE* wav); + void fill_wavelines(); + void sel_wave(int xpos); + void draw_waveline(int xpos,int mode); + void draw_fft_sample(int nr); + void wav_listen(); + int freq2mnr(float freq) { + float fl_midinr= 12./logf(2)*logf(freq*mult*2); + return lrint(fl_midinr+mid_C); + } + void write_conf(FILE *conf); + void read_conf(bool initialize); +} wav2s; + +int max(int a,int b) { return a>=b ? a : b; } +int min(int a,int b) { return a<=b ? a : b; } +int minmax(int a, int x, int b) { return x>=b ? b : x<=a ? a : x; } +int idiv(int a,int b) { return (2*a+b)/(2*b); } + +bool Wav2Score::read_wav_head(const char *inf,FILE*& wav) { + if ((wav=fopen(inf,"r"))==0) + err("%s not opened",inf); + char word[20]; + short dum16; + int dum32, + er_nr=0; + if ( + fread(word,4,1,wav)!=1 && (er_nr=1) || strncmp(word,"RIFF",4) && (er_nr=2) || + fread(&dum32, 4,1,wav)!=1 && (er_nr=3) || // header size + fread(word, 8,1,wav)!=1 && (er_nr=4) || strncmp(word,"WAVEfmt ",8) && (er_nr=5) || + fread(&dum32, 4,1,wav)!=1 && (er_nr=6) || dum32!=16 && (er_nr=7) || // chunk size + fread(&dum16, 2,1,wav)!=1 && (er_nr=8) // format tag (1 = uncompressed PCM) + ) goto error; + + if (dum16!=1) { alert("format = %d, should be 1",dum16); return false; } + if (fread(&dum16, 2,1,wav)!=1 && (er_nr=11)) // no of channels + goto error; + if (dum16==2) stereo=true; + else if (dum16==1) stereo=false; + else { alert("nr channels = %d, should be 1 or 2",dum16); return false; } + if (fread(&dum32, 4,1,wav)!=1 && (er_nr=13)) // rate + goto error; + if (dum32!=SAMPLE_RATE) { alert("rate = %d, must be %d",dum32,SAMPLE_RATE); return false; } + if (fread(&dum32, 4,1,wav)!=1 && (er_nr=14)) // average bytes/sec + goto error; + if (dum32!= SAMPLE_RATE* (stereo ? 4 : 2)) { + alert("byte/sec = %d, must be 2*%d",dum32,SAMPLE_RATE); return false; + } + if (fread(&dum16, 2,1,wav)!=1 && (er_nr=16) || // block align + fread(&dum16, 2,1,wav)!=1 && (er_nr=17)) // bits per sample + goto error; + if (dum16!=16) { + alert("bits per sample is %d, must be 16",dum16); return false; + } + if (fread(word, 4,1,wav)!=1 && (er_nr=19)) + goto error; + if (strncmp(word,"data",4)) { + word[4]=0; alert("word: '%s' (expected: 'data')",word); return false; + } + if (fread(&data_size, 4,1,wav)!=1 && (er_nr=21)) // sample length + goto error; + wave_init=true; + return true; + + error: + alert("format error (nr %d) in wave file",er_nr); + return false; +} + +bool Wav2Score::read_wav(FILE *wav,short *fb1,short *fb2,const int bufsize) { + static short *sbuf=new short[stereo ? 2*bufsize : bufsize]; + if (fread((char*)sbuf,stereo ? 4*bufsize : 2*bufsize,1,wav)!=1) { + printf("size=%d fft-samples=%d\n",data_size,nr_fft_samples); + return false; + } + if (stereo) + for (int n=0;n=sclin_max) return false; + lnr=lnr2; + sign=s[ind]; + return true; +} + +struct AmucOutput { + FILE *out; + float min_scale; + bool out_ok, + note_on; + int the_mnr, + start_time, // start time of last note + signsmode; + AmucOutput(): + out(0), + signsmode(0) { + } + void init() { + out_ok=false; + if ((out=fopen("fft.sco","w"))==0) + err("Could not open file fft.sco\n"); + out_ok=true; + the_mnr=0; + note_on=false; + fputs("4m8\n7m0e0g0k0 \"fft-tune\" ",out); + } + void close() { + if (out) fclose(out); + } + int time2time(int t) { // rounded float division, yields best results + static float mult=wav2s.buf_size / 2048. / 6. * wav2s.dur_inc; + return wav2s.use_each_sample ? t : lrint(t * mult); + } + void add_note(int midi_nr,int time) { + if (debug) + printf("mnr=%d the_mnr=%d time=%d st_t=%d dur=%d n_o=%d\n",midi_nr,the_mnr,time,start_time,time2time(time-start_time),note_on); + if (midi_nr!=the_mnr) { + if (note_on) { + int dur; + if (time-start_time>0 && (dur=time2time(time-start_time))>0) { + uchar lnr,sign; + bool ok=midinr_to_lnr(the_mnr,lnr,sign,signsmode); + if (ok) { + fprintf(out,"2L%dN%dd%di%ds0c0g0 ",lnr,time2time(start_time),dur,sign); + } + } + if (midi_nr==0) { + note_on=false; + the_mnr=0; + } + else { + start_time=time; + the_mnr=midi_nr; + } + } + else if (midi_nr>0) { + start_time=time; + the_mnr=midi_nr; + note_on=true; + } + } + } +} ao; + +void Wav2Score::init() { + the_xpos1=the_xpos2=-1; + wave_init=false; + hamming=new float[buf_size]; + const float A = 0.54, + B = 0.46; + for (int i=0;i a=-y1 + y2/2 + y2=4*a+2*b b=2*y1 - y2/2 +*/ + if (y0>y1 || y10.94; +} + +void Wav2Score::eval(float *buffer,float *buffer2) { // buffer size = buf_size + int i, + lst_top=-1, + maxval=0, + maxmag=0, + mag; + float toploc[peaks_max]; // location of ampl tops + bool above_thresh=false; + SpectData *sd=0; + if (gui) sd=&spectdata[called]; + + for (i=0;imaxmag=maxmag; + // now buf_size/2 buffer values valid + if (maxmag<200) { + if (gui) sd->skipped=true; + else ao.add_note(0,called); + return; + } + for (i=max(start,2);iint(maxmag*spect_thresh)) { + if (!above_thresh) { + above_thresh=true; + if (lst_top>=peaks_max-1) break; + ++lst_top; + maxval=0; + for (;i10) { + sd->spectlines[i]=lrint(buffer[i]) * mult_spl / maxmag; + sd->lst_sline=i; + } + else sd->spectlines[i]=0; + } + float tmp, + freq=0.; + bool valid=false; + for (i=0;i<=lst_top;++i) { + if (debug) printf("freq=%.1f valid=%d\n",freq,valid); + if (toploc[i]>1.) { + if (!valid) { valid=true; freq=toploc[i]; } + else if (near_eq(toploc[i],1.5*freq)) { tmp=toploc[i]/3; if (tmp>=start) freq=tmp; } + else if (near_eq(toploc[i],2*freq)) { tmp=toploc[i]/2; if (tmp>=start) freq=tmp; } + else if (near_eq(toploc[i],3*freq)) { tmp=toploc[i]/3; if (tmp>=start) freq=tmp; } + else if (near_eq(toploc[i],4*freq)) { tmp=toploc[i]/4; if (tmp>=start) freq=tmp; } + else if (toploc[i]>4*freq) break; + else { valid=false; break; } + } + else { + valid=false; break; + } + } + /* ln(2) = 0.69 -> delta midinr = 12 + freq = buf_size -> frequency = SAMPLE_RATE + Amuc mid C: 261.6/2 Hz + frequency = 130.8 -> freq = 130.8*buf_size/SAMPLE_RATE -> midinr = 60 + midinr = 12/ln(2)*ln(freq) + 60 + */ + int midinr=0; + if (gui) { + if (valid) { + if (!sd->asf_valid) { // midnr already assigned by read_conf() ? + midinr=wav2s.freq2mnr(freq); + sd->midinr=midinr; + } + sd->base_freq=freq; + sd->nr_peaks=lst_top; + sd->bf_valid=true; + for (i=0;i<=lst_top;++i) sd->peaks[i]=toploc[i]; + } + else sd->nr_peaks=-1; + } + else { + if (valid) { + midinr=wav2s.freq2mnr(freq); + if (debug) printf("freq=%.1f midinr=%d\n",freq,midinr); + ao.add_note(midinr,called); + } + else ao.add_note(0,called); + } +} + +void Wav2Score::eval_wavf(FILE *wav) { + int i; + float *buffer1=new float[buf_size], + *buffer2=new float[buf_size]; + short *sbuf1=new short[buf_size], + *sbuf2=new short[buf_size]; + for (called=0;calledval1) ylo1=val1; if (ylo2>val2) ylo2=val2; + } + } + WavLin& wl=wavelines[n1]; + wl.yhi1=yhi1; wl.ylo1=ylo1; + wl.yhi2=yhi2; wl.ylo2=ylo2; + } + } + else { + for (n1=0;n1val) ylo=val; + } + } + wavelines[n1].yhi1=yhi?yhi:ylo; + wavelines[n1].ylo1=ylo?ylo:yhi; + } + } +} + +static void draw_line(int xpos,int yhi,int ylo,int mid) { + if (yhi==ylo) + gui->wave_win->draw_point(Point(xpos,yhi+mid)); + else + gui->wave_win->draw_line(Point(xpos,yhi+mid),Point(xpos,ylo+mid)); +} + +void Wav2Score::draw_waveline(int xpos,int mode) { + static int mid1= stereo && !select_chan ? wwh/4 : wwh/2, + mid2=wwh*3/4; + int yhi,ylo; + WavLin &wl=wavelines[xpos]; + set_width(1); + if (mode) { + switch (mode) { + case eSelect1: + case eSelect2: + set_color(mode==eSelect1 ? cRed : cBlack); + gui->wave_win->draw_line(Point(xpos,0),Point(xpos,wwh)); + set_color(cGrey); + break; + case eUnselect1: + case eUnselect2: + set_color(cWaveBgr); + gui->wave_win->draw_line(Point(xpos,0),Point(xpos,wwh)); + set_color(cWave); + break; + } + } + else set_color(cWave); + if (stereo) { + draw_line(xpos,wl.yhi1,wl.ylo1,mid1); + if (!select_chan) + draw_line(xpos,wl.yhi2,wl.ylo2,mid2); + } + else + draw_line(xpos,wl.yhi1,wl.ylo1,mid1); +} + +void draw_marks(SpectData &sd,int sl_dist,bool draw_harm) { + int n,x; + set_color(cGreen); + for (n=0;n<=sd.nr_peaks;++n) { + x=lrint(sd.peaks[n] * sl_dist); + gui->spectrum_win->fill_triangle(Point(x-3,0),Point(x+3,0),Point(x,5)); + } + if (sd.bf_valid) { + set_color(cRed); + x=lrint(sd.base_freq * sl_dist); + gui->spectrum_win->fill_triangle(Point(x-3,0),Point(x+3,0),Point(x,5)); + } + if (sd.asf_valid) { + set_color(cBlack); + x=lrint(sd.assigned_freq * sl_dist); + gui->spectrum_win->fill_triangle(Point(x-3,0),Point(x+3,0),Point(x,5)); + if (draw_harm) { + for (int harm=2;;++harm) { + x=lrint(sd.assigned_freq * sl_dist * harm); + if (int(x)>spect_size) break; + gui->spectrum_win->fill_triangle(Point(x-2,0),Point(x+2,0),Point(x,5)); + } + } + } +} + +void draw_spectrum(Id) { + int n, + bs=wav2s.buf_size, + nr=gui->f_c_value(), + sl_dist= bs==512 ? 8 : bs==8192 ? 2 : 4; + BgrWin *spwin=gui->spectrum_win; + if (nrdy-20, + ythresh=ybot-int(wav2s.mult_spl*wav2s.spect_thresh); + + set_width_color(1,cRed); + int start=wav2s.start*sl_dist, // draw spectrum threshold + stop=wav2s.stop ? min(wav2s.stop*sl_dist,spect_size) : spect_size; + for (n=start;ndraw_line(Point(n,ythresh),Point(n+5,ythresh)); + if (start>0) + spwin->draw_line(Point(start-1,ythresh-4),Point(start-1,ythresh+4)); + if (wav2s.stop) + spwin->draw_line(Point(stop+1,ythresh-4),Point(stop+1,ythresh+4)); + + set_color(cBlack); + SpectData &sd=wav2s.spectdata[nr]; + for (n=0;ndraw_line(Point(sl_dist*n,ybot),Point(sl_dist*n,ybot-wav2s.spectdata[nr].spectlines[n])); + if (!sd.skipped && (sd.midinr || sd.asf_valid)) + draw_marks(sd,sl_dist,false); + } + int dist_250hz=bs*250*sl_dist/SAMPLE_RATE; + static int y=spwin->dy-18; + set_color(cFreqScale); + spwin->fill_rectangle(Rect(0,y,spect_size,18)); + set_width(2); + for (n=0;;++n) { + int x=n*dist_250hz; + if (x>=spect_size) break; + set_color(cRed); + spwin->draw_line(Point(x,y),Point(x,y+6)); + if (n==0) + xft_draw_string(spwin->xft_win,xft_Black,Point(0,y+16),"0"); + else { + char txt[20]; + if (n%4) { + if (n%2==0) { + sprintf(txt,"%.1f",n/4.); + xft_draw_string(spwin->xft_win,xft_Black,Point(x-8,y+16),txt); + } + else if (bs>=2048) { + sprintf(txt,"%.2f",n/4.); + xft_draw_string(spwin->xft_win,xft_Black,Point(x-8,y+16),txt); + } + } + else { + sprintf(txt,"%dKHz",n/4); + xft_draw_string(spwin->xft_win,xft_Black,Point(x-8,y+16),txt); + } + } + } +} + +void mouse_down(Id,int x,int y,int butnr) { + switch (butnr) { + case 2: { + int n, + bs=wav2s.buf_size, + nr=gui->f_c_value(), + sl_dist= bs==512 ? 8 : bs==8192 ? 2 : 4, + sline_nr=x/sl_dist; // spectrum line nr + bool valid; + Arr lines=wav2s.spectdata[nr].spectlines; + uchar val[3] = { lines[sline_nr-1],lines[sline_nr],lines[sline_nr+1] }; + SpectData &sd=wav2s.spectdata[nr]; + if (val[0]>2 && val[1]>2 && val[2]>2) { + float peak=sline_nr-1+index_of_peak(val[0],val[1],val[2],valid); + int midinr=wav2s.freq2mnr(peak); + sd.midinr=midinr; + sd.assigned_freq=peak; + sd.asf_valid=true; + gui->spectrum_win->clear(Rect(0,0,spect_size,5)); + draw_marks(sd,sl_dist,true); + } + else { + sd.midinr=0; + sd.asf_valid=true; //false; + sd.assigned_freq=0.; + gui->spectrum_win->clear(Rect(0,0,spect_size,5)); + draw_marks(sd,sl_dist,false); + } + gui->draw_info(nr); + } + break; + case 1: + case 3: { + int nr=gui->f_c_value(); + if (butnr==1) { if (nr>0) --nr; else break; } + else { if (nrcourse) { + int val=nr/100; + set_text(gui->course->text,"%d",val*100); + gui->course->set_hsval(val,false,true); // not fire, draw + } + gui->fine->set_hsval(nr%100,true,true); // fire, draw + } + break; + } +} +static void display_cmd(Rect,Id); + +void Gui::draw_info(int fft_nr) { + char txt[50], + *p; + info->reset(); + sprintf(txt,"fft-sample %d",fft_nr); + info->add_text(txt); + SpectData *sd=&wav2s.spectdata[fft_nr], + *sd1; + sprintf(txt,"measure %d",ao.time2time(fft_nr)/8); + info->add_text(txt); + info->add_text("midi-numbers:"); + for (p=txt,sd1=sd-2;sd1midinr>0) + p+=sprintf(p,"%d",sd1->midinr); + else if (sd1->skipped) + p+=sprintf(p,"-"); + else + p+=sprintf(p,"?"); + if (sd==sd1) *p++=']'; + *p++=' '; + } + *p=0; + info->add_text(txt,xft_Red); + sprintf(txt,"max peak %d",sd->maxmag); + info->add_text(txt); + info->draw_text(); + if (wav2s.sv_win) { + clear(wav2s.sv_win->svwin); + display_cmd(Rect(0,0,0,0),0); + } +} + +void Wav2Score::sel_wave(int xpos) { + int fft_nr=min(nr_fft_samples-1,idiv(xpos*nr_fft_samples,ww_size)); + if (gui->course) { + int val=fft_nr/100; + set_text(gui->course->text,"%d",val*100); + gui->course->set_hsval(val,false,true); // not fire, draw + } + gui->fine->set_hsval(fft_nr%100,true,true); // fire, draw +} + +void Wav2Score::eval_wavf_gui(FILE *wav) { + int n; + float *buffer1=new float[buf_size], + *buffer2=new float[buf_size]; + short *sbuf1=new short[buf_size], + *sbuf2=new short[buf_size]; + shortbuf=new short[nr_wav_samples*(stereo ? 2 : 1)]; + spectdata.init(nr_fft_samples); + for (n=0;nf_c_value()); } + +void Wav2Score::draw_fft_sample(int nr) { + static int mid=wwh/2, + mid1=wwh/4, + mid2=wwh*3/4, + div=(stereo && !select_chan ? 0x20000 : 0x10000)/wwh; + set_width_color(1,cWave); + if (stereo) { + short *sbuf=shortbuf+nr*buf_size; // buffers overlap + Point points1[fftw_size], + points2[fftw_size]; + for (int n=0;nfft_win->draw_lines(points1,fftw_size); + if (!select_chan) gui->fft_win->draw_lines(points2,fftw_size); + } + else { + short *sbuf=shortbuf+nr*buf_size/2; // buffers overlap + Point points[fftw_size]; + for (int n=0;nfft_win->draw_lines(points,fftw_size); + } +} + +void select_wave(Id,int xpos,int ypos,int butnr) { + if (butnr==1) wav2s.sel_wave(xpos); +} + +int Gui::f_c_value() { + int val=fine->value(); + if (course) val+=100*course->value(); + return min(wav2s.nr_fft_samples-1,val); +} + +void fine_course_cmd() { + int fcval=gui->f_c_value(); + gui->spectrum_win->clear(); + draw_spectrum(0); + gui->draw_info(fcval); + int xpos=fcval * ww_size / wav2s.nr_fft_samples; + if (wav2s.the_xpos1!=xpos) { + if (wav2s.the_xpos1>=0) + wav2s.draw_waveline(wav2s.the_xpos1,eUnselect1); + wav2s.the_xpos1=xpos; + wav2s.draw_waveline(xpos,eSelect1); + } + gui->fft_win->clear(); + wav2s.draw_fft_sample(fcval); +} + +void fine_cmd(Id,int val,int fire,char*& txt,bool) { set_text(txt,"%d",val); fine_course_cmd(); } +void course_cmd(Id,int val,int,char*& txt,bool) { set_text(txt,"%d",val*100); fine_course_cmd(); } + +void *wave_listen(void *arg) { + wav2s.wav_listen(); + return 0; +} + +void Wav2Score::wav_listen() { + SndInterf *snd_interf=new SndInterf(); + short buffer[IBsize*2]; + if (snd_interf->okay) { + int i, + ind; + bool stop=false; + ind=gui->f_c_value() * buf_size/2; + for (;!stop && !stop_requested;ind+=IBsize) { + for (i=0;isnd_write(buffer); + } + } + delete snd_interf; + send_uev('arro'); // for play button + i_am_playing=false; +} + +void right_arrow(uint win,XftDraw*,Id,int,int) { + const int x=6,y=9; + static Point pts[3]={ Point(x,y-4),Point(x+6,y),Point(x,y+4) }; + fill_polygon(win,cBlack,pts,3); +} + +void square(uint win,XftDraw*,Id,int,int) { + fill_rectangle(win,cRed,Rect(6,6,6,7)); +} + +static void write_sco_cmd(Id) { + int nr; + ao.init(); + SpectData *sd; + for (nr=0;nrskipped || !sd->midinr) ao.add_note(0,nr); + else ao.add_note(sd->midinr,nr); + } + ao.add_note(0,nr); // the last note + ao.close(); +} + +static void write_conff_cmd(Id) { // write config file + FILE *config; + if (!(config=fopen(configf.s,"w"))) + alert("config file %s not written",configf.s); + else + wav2s.write_conf(config); + fclose(config); +} + +static void dis_sv_cmd(Id) { + if (wav2s.sv_win) map_window(wav2s.sv_win->svwin); + else wav2s.sv_win=new ScoreView(Point(400,450)); +} + +void play_cmd(Id) { + if (!wav2s.i_am_playing) { + wav2s.i_am_playing=true; + wav2s.stop_requested=false; + gui->play->label.draw=square; + pthread_t play_thread; + pthread_create(&play_thread, 0, wave_listen, 0); // if exit: wav2s.i_am_playing = false + } + else { + wav2s.stop_requested=true; + gui->play->label.draw=right_arrow; + } +} + +void draw_topw(XftDraw *xft_win,Rect expose_rect) { + static char input_wave_title[100], + fft_win_title[50]; + if (!input_wave_title[0]) { + snprintf(fft_win_title,50,"FFT WINDOW (%d samples)",wav2s.buf_size); + snprintf(input_wave_title,100,"INPUT WAVE (file: %s)",inf); + } + static Point pts[5]={ + Point(2,gui->spectrum_win->y-4), + Point(2,gui->wave_win->y-4), + Point(2,gui->info->y-4), + Point(2,gui->play->y+14), + Point(gui->fft_win->x,gui->fft_win->y-4) + }; + static XftText txt[5]= { + XftText(top_win->win,xft_win,"SPECTRUM",pts[0],BOLDfont), + XftText(top_win->win,xft_win,input_wave_title,pts[1],BOLDfont), + XftText(top_win->win,xft_win,"INFO",pts[2],BOLDfont), + XftText(top_win->win,xft_win,"PLAY",pts[3],BOLDfont), + XftText(top_win->win,xft_win,fft_win_title,pts[4],BOLDfont) + }; + for (int i=0;i<5;++i) { + if (a_in_b(expose_rect,Rect(pts[i].x,pts[i].y-10,200,12))) + txt[i].draw(); + } +} + +Gui::Gui() { + int y=16; + spectrum_win=new BgrWin(top_win->win,Rect(2,y,spect_size,100),FN,draw_spectrum,mouse_down,0,0,cLightYellow); + wav2s.mult_spl=spectrum_win->dy-26; + + y+=124; + int nr=wav2s.nr_fft_samples/100; + if (nr>0) { + fine=new HSlider(top_win,Rect(2,y,spect_size,0),FN,0,99,"FFT SAMPLE - fine","0","99",fine_cmd,cForeground); + char *txt=new char[10]; + sprintf(txt,"%d",nr); + course=new HSlider(top_win,Rect(2,y+42,minmax(40,nr*10+10,spect_size),0),FN,0,nr,"FFT SAMPLE - course","0",txt,course_cmd,cForeground); + } + else { + nr=wav2s.nr_fft_samples; + char *txt=new char[10]; + sprintf(txt,"%d",nr-1); + fine=new HSlider(top_win,Rect(2,y,min(nr*10+10,spect_size),0),FN,0,nr-1,"FFT SAMPLE","0",txt,fine_cmd,cForeground); + course=0; + } + + y+=88; + wav2s.wwh=wav2s.stereo && !wav2s.select_chan ? 160 :80; + wave_win=new BgrWin(top_win->win,Rect(2,y,ww_size,wav2s.wwh),FN,draw_waves,select_wave,0,0,cWaveBgr); + + y+=wav2s.stereo && !wav2s.select_chan ? 182 : 102; + info=new TextWin(top_win->win,Rect(2,y,100,0),FN,5); + + fft_win=new BgrWin(top_win->win,Rect(106,y,fftw_size,wav2s.wwh),FN,draw_fft_sample,0,0,0,cWaveBgr); + + y+=wav2s.wwh+4; + play=new Button(top_win->win,Rect(40,y,20,20),FN,right_arrow,play_cmd); + y+=2; + button_style.set(1,cForeground,0); + new Button(top_win->win,Rect(106,y,75,0),FN,"write score file: fft.sco",write_sco_cmd); + y+=20; + static char buf[100]; + snprintf(buf,100,"write configuration file: %s",configf.s); + new Button(top_win->win,Rect(106,y,75,0),FN,buf,write_conff_cmd); + y+=20; + new Button(top_win->win,Rect(106,y,0,0),FN,"score display window",dis_sv_cmd); +} + +void Wav2Score::write_conf(FILE *conf) { + fprintf(conf,"wave_file = %s\n",inf); + fprintf(conf,"win = %d\n",buf_size); + fprintf(conf,"min = %d\n",min_midinr); + fprintf(conf,"max = %d\n",max_midinr); + fprintf(conf,"ch = %c\n",select_chan); + fprintf(conf,"c = %.1f\n",mid_C); + fprintf(conf,"th = %.3f\n",spect_thresh); + fprintf(conf,"dur = %.3f\n",dur_inc); + fprintf(conf,"signs = %d\n",ao.signsmode); + fprintf(conf,"es = %d\n",use_each_sample); + fprintf(conf,"assigned_freq ="); + SpectData *sd; + int nr; + for (nr=0;nrasf_valid) + fprintf(conf," %d:%d,%.1f",nr,sd->midinr,sd->assigned_freq); + } + putc('\n',conf); +} + +void Wav2Score::read_conf(bool initialize) { + static FILE *conf; + Str str; + if (initialize) { + if (!(conf=fopen(configf.s,"r"))) { + alert("warning: config file %s not found",configf.s); + return; + } + for (;conf;) { + str.rword(conf," ="); + if (str=="assigned_freq") // rest later + return; + if (str=="wave_file") { + str.rword(conf," \n"); + if (str!=inf) { + alert("error: wave file mismatch in config file"); + fclose(conf); conf=0; + } + } + else if (str=="win") { + str.rword(conf," \n"); + int bs=atoi(str.s); + if (wav2s.buf_size!=bs) { + if (wav2s.buf_size && options) alert("%s: -win parameter set to %d",configf.s,bs); + wav2s.buf_size=bs; + } + } + else if (str=="th") { + str.rword(conf," \n"); + float th=atof(str.s); + if (th>0.001 && fabs(wav2s.spect_thresh/th-1) > 0.001) { + if (options) alert("%s: -th parameter set to %.3f",configf.s,th); + wav2s.spect_thresh=th; + } + } + else if (str=="dur") { + str.rword(conf," \n"); + float d=atof(str.s); + if (d>0.01 && fabs(wav2s.dur_inc/d-1) > 0.001) { + if (options) alert("%s: -dur parameter set to %.3f",configf.s,d); + wav2s.dur_inc=d; + } + } + else if (str=="ch") { + str.rword(conf," \n"); + char ch=str.s[0]; + if (wav2s.select_chan!=ch) { + if (options) alert("%s: -ch parameter set to %c",configf.s,ch); + wav2s.select_chan=ch; + } + } + else if (str=="es") { + str.rword(conf," \n"); + bool es=atoi(str.s); + if (es!=wav2s.use_each_sample) { + if (options) alert("%s: -es set to %d",configf.s,es); + wav2s.use_each_sample=es; + } + } + else if (str=="min") { + str.rword(conf," \n"); + wav2s.min_midinr=atoi(str.s); + } + else if (str=="max") { + str.rword(conf," \n"); + wav2s.max_midinr=atoi(str.s); + } + else if (str=="c") { + str.rword(conf," \n"); + wav2s.mid_C=atof(str.s); + } + else if (str=="signs") { + str.rword(conf," \n"); + ao.signsmode=atoi(str.s); + } + else { + alert("unknown keyword '%s' in config file",str.s); + fclose(conf); conf=0; + } + } + } + if (!conf) { + if (initialize) alert("config file error"); + return; + } + // so now at keyword 'assigned_freq' + int nr,mnr, + res; + float asf; + for (;;) { + res=fscanf(conf," %d:%d,%f",&nr,&mnr,&asf); + if (res!=3) break; + SpectData &sd=spectdata[nr]; + sd.midinr=mnr; + sd.assigned_freq=asf; + sd.asf_valid=true; + } + fclose(conf); +} + +static void display_cmd(Rect exp_rect,Id) { + int i; + const int fc_val=gui->f_c_value(), + half=notes_max/2; + SpectData *sd=&wav2s.spectdata[fc_val], + *sd1; + SubWin *sv_win=wav2s.sv_win->subw; + for (i=0,sd1=sd-half;sd1win,cLightGrey,Rect(x,0,note_len,sv_win->dy)); + if (sd1->midinr>0) { + y=sv_win->dy-(sd1->midinr-midi_min)*sclin_dist; + draw_line(sv_win->win,3,cBlack,Point(x,y),Point(x+note_len,y)); + } + else if (sd1->skipped); + else { + y=(midi_max-midi_min)/2*sclin_dist; + draw_line(sv_win->win,1,cRed,Point(x,y),Point(x+note_len,y)); + } + } +} + +static void hide_diswin(Id) { + hide_window(wav2s.sv_win->svwin); +} + +ScoreView::ScoreView(Point top) { + subw=new SubWin("score display",Rect(top.x,top.y,notes_max*note_len,(midi_max-midi_min)*sclin_dist), + true,cLightYellow,display_cmd,hide_diswin); + svwin=subw->win; +}; + +void do_atexit() { +} + +void handle_uev(int cmd,int par1,int par2) { + switch (cmd) { + case 'updi': // update select pointer in wave display (par1 = previous xpos, par2 = xpos) + if (par1>=0) + wav2s.draw_waveline(par1,eUnselect2); + wav2s.draw_waveline(par2,eSelect2); + break; + case 'arro': + gui->play->label.draw=right_arrow; + gui->play->draw_button(); + break; + default: alert("handle_uev: cmd=%d",cmd); + } +} + +int main(int argc, char ** argv) { + bool with_gui=true; + int nr; + for (int an=1;an"); + puts("Options:"); + puts(" -h : print usage info and exit"); + puts(" -nogui : command-line interface"); + puts(" -db : debug"); + puts(" -ch L|R : if stereo, process only left or right channel"); + puts(" -win : fft window = samples (between 512 and 8192, default: 2048)"); + puts(" -min : midi number of lowest fundamental (default: 0)"); + puts(" -max : midi number of highest harmonic (default: dependant on -win parameter)"); + puts(" -c : midi number for middle-C, default: 48.0"); + puts(" -th : threshold level for spectrum lines, default: 0.2"); + puts(" -dur : modify samples/note-duration, default: 1.0"); + puts(" -es : use each sample:"); + puts(" samples/note-duration factor such that each valid sample yields 1 note unit"); + puts(" -signs hi|lo : preference for sharp or flat accidentals in output score file"); + puts("Output file:"); + puts(" fft.sco"); + exit(0); + } + options=false; + if (!strcmp(argv[an],"-gui")) + err("option -gui is deprecated"); + if (!strcmp(argv[an],"-db")) + debug=true; + else if (!strcmp(argv[an],"-nogui")) + with_gui=false; + else if (!strcmp(argv[an],"-win")) { + if (++an>=argc) err("-win parameter missing"); + nr=atoi(argv[an]); + if (nr<500 || nr>8192) err("-win option = %d, expected 512 - 8192",nr); + for (int shift=0;shift<5;++shift) { + if (nr<(512<=argc) err("-min parameter missing"); + nr=atoi(argv[an]); + if (nr<0 || nr>100) err("-min value out-of-range"); + wav2s.min_midinr=nr; + } + else if (!strcmp(argv[an],"-max")) { + if (++an>=argc) err("-max parameter missing"); + nr=atoi(argv[an]); + if (nr<0 ||nr>128) err("-max value out-of-range"); + wav2s.max_midinr=nr; + } + else if (!strcmp(argv[an],"-ch")) { + if (++an>=argc) err("-ch parameter missing"); + wav2s.select_chan=argv[an][0]; + if (!strchr("LR",wav2s.select_chan)) err("-ch must be L or R"); + } + else if (!strcmp(argv[an],"-c")) { + if (++an>=argc) err("-c parameter missing"); + wav2s.mid_C=atof(argv[an]); + } + else if (!strcmp(argv[an],"-th")) { + if (++an>=argc) err("-th parameter missing"); + float th=atof(argv[an]); + if (th<0.05 || th>1.0) err("bad -th value (expected 0.05 - 1.0)"); + wav2s.spect_thresh=th; + } + else if (!strcmp(argv[an],"-dur")) { + if (++an>=argc) err("-dur parameter missing"); + wav2s.dur_inc=atof(argv[an]); + if (wav2s.dur_inc<0.1 || wav2s.dur_inc>100) err("bad -dur value"); + } + else if (!strcmp(argv[an],"-signs")) { + if (++an>=argc) err("-signs parameter missing"); + if (!strcmp(argv[an],"hi")) ao.signsmode=eHi; + else if (!strcmp(argv[an],"lo")) ao.signsmode=eLo; + else err("bad -signs value"); + } + else if (!strcmp(argv[an],"-es")) { + wav2s.use_each_sample=true; + } + else + err("Unexpected option %s (use -h for help)",argv[an]); + } + else inf=argv[an]; + } + if (!inf) err("no wave file (use -h for help)"); + if (argc>2 && with_gui) options=true; + + configf.cpy(inf); + configf.new_ext(".w2s"); + if (with_gui) { + init_xwindows(); // needed for alert messages + wav2s.read_conf(true); + if (!wav2s.buf_size) wav2s.buf_size=2048; + } + else { + if (!wav2s.buf_size) wav2s.buf_size=2048; + } + wav2s.init(); + FILE *wav; + if (!wav2s.read_wav_head(inf,wav)) exit(1); + wav2s.nr_wav_samples=wav2s.data_size/(wav2s.stereo ? 4 : 2); + wav2s.nr_fft_samples=max(1,wav2s.nr_wav_samples*2/wav2s.buf_size-1); // times 2, minus 1, because buffers overlap + int total_height= wav2s.stereo && !wav2s.select_chan ? 636 : 478; + + if (with_gui) { + //init_xwindows(); + cGreen=calc_color("#00C000"); + cWave=calc_color("#009000"); + cWaveBgr=calc_color("#D0E0F0"); // sky blue + cLightYellow=calc_color("#FFFFD0"); + cFreqScale=calc_color("#E0E0B0"); + cLightGrey=calc_color("#D7D7D7"); + top_win=create_top_window("wav2score",Rect(100,0,total_width,total_height),false,draw_topw,cForeground); + set_icon(top_win->win,create_pixmap(wav2s_icon).pm,16,16); + button_style.set(0,0,1); + slider_style.set(1,true); + checkbox_style.set(1,0,0); + gui=new Gui(); + wav2s.eval_wavf_gui(wav); + if (wav2s.nr_fft_samples<=1) + gui->fine->hide(); + else + gui->fine->set_hsval(min(wav2s.nr_fft_samples/2,10),true,true); + map_top_window(); + run_xwindows(); + } + else { + ao.init(); + wav2s.eval_wavf(wav); + ao.close(); + const char *mode; + if (wav2s.stereo) { + if (wav2s.select_chan=='R') mode="stereo, right channel"; + else if (wav2s.select_chan=='L') mode="stereo, left channel"; + else mode="stereo"; + } + else + mode="mono"; + printf("%d FFT-samples (%s). Score file fft.sco created.\n", + wav2s.nr_fft_samples,mode); + } + return 0; +} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..132712d --- /dev/null +++ b/src/Makefile @@ -0,0 +1,36 @@ +# Makefile for Amuc - the A'dam Music Composer +# Using environment variable DBG, which might be: "-g -Wuninitialized -Wall -Wno-non-virtual-dtor" +include ../Makefile.inc + +CC=g++ +A2PS_DIR=../src-abcm2ps +W2S_DIR=../src-wav2score + +OBJS=amuc.o x-widgets.o sound.o str.o midi-out.o ps-out.o physical-mod.o bitmaps.o \ + read-waves.o dump-wav.o mono-synth.o snd-interface.o midi-in.o chords.o \ + midi-keyb.o midi-keyb-jack.o + +.SUFFIXES= + +amuc: $(OBJS) + $(CC) $(OBJS) $(A2PS_DIR)/abc2ps.a -o amuc $(LDFLAGS) + +%.o: %.cpp + $(CC) -c -O $(DBG) $(CFLAGS) -Wno-multichar $< + +amuc-headers.h: str.h x-widgets.h dump-wav.h colors.h midi-out.h templates.h ps-out.h \ + amuc.h read-waves.h physical-mod.h midi-keyb.h mono-synth.h sound.h \ + midi-in.h chords.h + touch amuc-headers.h + +amuc.o sound.o mono-synth.o read-waves.o physical-mod.o ps-out.o midi-in.o chords.o: amuc-headers.h +amuc.o: bitmaps.h snd-interface.h +sound.o: snd-interface.h +x-widgets.o: x-widgets.h +midi-out.o: colors.h midi-out.h +midi-in.o: colors.h +snd-interface.o: snd-interface.h +dump-wav.o: dump-wav.h +midi-keyb.o midi-keyb-jack.o: midi-keyb.h +str.o: str.h +bitmaps.o: bitmaps.h x-widgets.h diff --git a/src/amuc-headers.h b/src/amuc-headers.h new file mode 100644 index 0000000..17106fa --- /dev/null +++ b/src/amuc-headers.h @@ -0,0 +1,15 @@ +#include "str.h" +#include "x-widgets.h" +#include "dump-wav.h" +#include "colors.h" +#include "midi-out.h" +#include "midi-in.h" +#include "templates.h" +#include "ps-out.h" +#include "amuc.h" +#include "read-waves.h" +#include "chords.h" +#include "physical-mod.h" +#include "midi-keyb.h" +#include "mono-synth.h" +#include "sound.h" diff --git a/src/amuc.cpp b/src/amuc.cpp new file mode 100644 index 0000000..b26773c --- /dev/null +++ b/src/amuc.cpp @@ -0,0 +1,6682 @@ +#include // for keyboard symbols +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bitmaps.h" +#include "amuc-headers.h" +#include "snd-interface.h" + +const int + sect_scv=40, // initial scoreview sections in ScLine + sect_dmax=150, // max drawable sections + wavef_max=100, // max wave sample files + sect_mus_max=66, // max sections in music ScLine + sclin_dist=4, // line distance + p_sclin_dist=3, // line distance, draw_mode ePiano + y_off=2, // y offset of score lines + p_y_off=-48, // idem if draw_mode = ePiano + s_wid=14, // width of signs window + sced_wid=56, // score edit window 2 + sect_length=6, + nop=-99, + eo_arr=-98, // end of array + scrollbar_wid=8, // scrollbars + view_vmax=660, // total hight + view_hmax=650, // total width + mnr_height=12, // measure nr view + scview_vmax=3+y_off+mnr_height+sclin_max*sclin_dist+scrollbar_wid, // height of score view + tnview_height=50, // musicview tune names + rbutview_left=247, // distance left side of radio-button views and right side + but_dist=18, // button distance + slider_height=10+TDIST, // horizontal slider height + ictrl_height=126, // instrument control view + score_info_len=81, // at right side of scores + checkbox_height=15, // checkbox height + scview_wid=s_wid+sect_scv*sect_length, // score part in score view + mn_max=1<=b ? a : b; } +int minmax(int a, int x, int b) { return x>=b ? b : x<=a ? a : x; } + +int color_nr(const char *coln,bool *ok) { + if (ok) *ok=true; + for (int i=0;i %d multi notes",max); return 0; } + return ++end; + } + void reset(int typ) { + if (typ==eMusic) { + for (int n=mn_end_start;n<=mn_end;++n) buf[n].reset(); + mn_end=mn_end_start; + } + else { + for (int n=0;n<=mn_tunes_end;++n) buf[n].reset(); + mn_tunes_end=0; + } + } +} mn_buf; + +struct TuneNames { + int dim, + lst_ind; + const char **buf; + TuneNames(): + dim(50), + lst_ind(-1), + buf(new const char*[dim]) { + } + void reset() { lst_ind=-1; } + int add(const char* item) { + if (lst_ind==dim-1) buf=re_alloc(buf,dim); + buf[++lst_ind]=strndup(item,50); + return lst_ind; + } + const char *txt(int ind) { + return ind<0 || ind>lst_ind ? "?" : buf[ind]; + } + void replace(int ind,const char* n_name) { + if (ind<0 || ind>lst_ind) + alert("TN::replace: ind=%d?",ind); + else + buf[ind]=strndup(n_name,50); + } +} tnames; + +const char* ind2tname(int ind) { return tnames.txt(ind); } + +struct SectData { + int snr; + uchar lnr,sign; + SectData(int _lnr,int _snr,int _sign): + snr(_snr),lnr(_lnr),sign(_sign) { } + bool operator==(SectData &sd) { return lnr==sd.lnr && snr==sd.snr; } + bool operator<(SectData &sd) { // sign is irrelevant + return snr==sd.snr ? lnr n6; + int gnr; // group nr +}; + +struct ScInfo { + struct IData { + char col; + short d0,d1,d2,d3; + bool b; + }; + uint tag; + union { + const char *text; + bool b; + IData idata; + short n5[5]; + int n; + MSynthData ms_data; + MidiData midi_data; + }; + ScInfo *next; + ShortBuffer wavedata; + ScInfo():tag(0),next(0) { } + ScInfo(uint t):tag(t),next(0) { } + ~ScInfo() { delete next; } + void add(ScInfo& info) { + ScInfo *sci; + if (!tag) *this=info; + else { + for (sci=this;sci->next;sci=sci->next); + sci->next=new ScInfo(info); + } + } + void reset() { + delete next; + tag=0; next=0; wavedata.reset(); + } +}; + +struct ExSample { + ScInfo *info; + int fsel_butnr; // button nr PhmCtrl::file_select + ExSample():info(0) { } +}; + +struct Scores { + int dim, + lst_score; + Score **buf; + Scores(): + dim(50), + lst_score(-1), + buf(new Score*[dim]) { + } + Score *new_score(const char* nam) { + if (lst_score==dim-1) + buf=re_alloc(buf,dim); + return (buf[++lst_score]=new Score(nam,sect_scv,0)); + } + Score *exist_name(const char *nam); + void swap(int ind1,int ind2); + void remove(Score*); + void reset() { + for (int n=0;n<=lst_score;++n) { + delete buf[n]; buf[n]=0; + } + lst_score=-1; + } + int get_index(Score *sc) { + for (int i=0;ilst_score) { + alert("Scores::[] ind=%d?",ind); + return buf[0]; + } + return buf[ind]; + } +} scores; + +int f_or_s_to_keynr(int flatn,int sharpn) { // for backwards compatibility + int f,s; + for (int knr=0;knract_meter=mtr; + } + break; + case eScore_start2: { + age=1; + int flatn,sharpn, + dummy; + scp=scores.new_score(0); if (!scp) return false; + res=sscanf(save_buf.s,"%*uc%da%*df%ds%dm%de%dg%d", + &dummy,&flatn,&sharpn,&scp->sc_meter, + &scp->end_sect,&scp->ngroup); + if (res==5 || res==6) { + if (res==6) age=2; + if (scp->end_sect) + scp->check_len(scp->end_sect+1); + scp->sc_key_nr=f_or_s_to_keynr(flatn,sharpn); + scp->tone2signs(scp->sc_key_nr); + } + else { + alert("bad code: %s",save_buf.s); + return false; + }; + if (fscanf(in,"%50s",textbuf)==1) + scp->name=tnames.add(textbuf); + } + break; + case eScore_start3: { + age=3; + scp=scores.new_score(0); if (!scp) return false; + res=sscanf(save_buf.s,"%*ua%*dm%de%dg%dk%d", + &scp->sc_meter, + &scp->end_sect,&scp->ngroup,&scp->sc_key_nr); + if (res==4) { + if (scp->end_sect) + scp->check_len(scp->end_sect+1); + scp->tone2signs(scp->sc_key_nr); + } + else { + alert("bad code: %s",save_buf.s); + return false; + }; + if (fscanf(in," \"%50[^\"]s",textbuf)==1) + scp->name=tnames.add(textbuf); + getc(in); // skip '"' + } + break; + case eScore_start4: { + age=4; + scp=scores.new_score(0); if (!scp) return false; + res=sscanf(save_buf.s,"%*um%de%dg%dk%d", + &scp->sc_meter, + &scp->end_sect,&scp->ngroup,&scp->sc_key_nr); + if (res==4) { + if (scp->end_sect) + scp->check_len(scp->end_sect+1); + scp->tone2signs(scp->sc_key_nr); + } + else { + alert("bad code: %s",save_buf.s); + return false; + }; + if (fscanf(in," \"%50[^\"]s",textbuf)==1) + scp->name=tnames.add(textbuf); + getc(in); // skip '"' + } + break; + case ePlayNote: { + int ind,snr,dur,sign,stacc_sampl,col,gnr=0,dlnr=0,dsnr=0,del_s=0,del_e=0; + if (age==1) { + res=sscanf(save_buf.s,"%*uL%dN%dd%di%ds%dc%da%*dp%d,%dD%d,%d", + &ind,&snr,&dur,&sign,&stacc_sampl,&col,&dlnr,&dsnr,&del_s,&del_e); + } + else if (age==3 || age==2) { + res=sscanf(save_buf.s,"%*uL%dN%dd%di%ds%dc%da%*dg%dp%d,%dD%d,%d", + &ind,&snr,&dur,&sign,&stacc_sampl,&col,&gnr,&dlnr,&dsnr,&del_s,&del_e); + } + else if (age==4) { + res=sscanf(save_buf.s,"%*uL%dN%dd%di%ds%dc%dg%dp%d,%dD%d,%d", + &ind,&snr,&dur,&sign,&stacc_sampl,&col,&gnr,&dlnr,&dsnr,&del_s,&del_e); + } + else { + alert("age=%d",age); + return false; + } + if (res<6 || res>11) { + alert("bad code: %s",save_buf.s); return false; + } + scp->check_len(snr+dur); // 1 extra + ScSection *start=scp->get_section(ind,snr), + *sec; + for (int i=0;;) { + sec=start; + if (sec->cat==ePlay) // multi note? + sec=start->get_the_section(0,0); + if (i==0) sec->del_start=del_s; + sec->cat=ePlay; + sec->s_col=col; + sec->sign=sign; + sec->stacc=0; + sec->sampled=0; + sec->s_group=gnr; + if (++i==dur) break; + ++start; + } + sec->stacc= stacc_sampl & 1; + sec->sampled= (stacc_sampl>>1) & 1; + sec->port_dlnr=dlnr; + sec->port_dsnr=dsnr; + sec->del_end=del_e; + } + break; + default: + if (opc==eScore_start1) alert("old score file?"); + else alert("unknown code %d",opc); + return false; + } + return true; + } +}; + +struct ScoreViewBase { + int draw_mode, + play_start, + play_stop, + leftside, // left side of window, set by scrollbar + piano_y_off,// y offset if draw_mode = ePiano + prev_drawmode, // previous draw_mode + sect_len, + scale_dep, // ePiano mode: shift lines depending on chordswin key or score key + key; // pressed key + bool zoomed, + keep_m_action; // shift key pressed? + const int sv_type; // 0 or eMusic + uint p_scline_col[24]; // ePiano mode: line colors + Score *score; + BgrWin *scview, // score + *tnview, // tune names + *mnrview; // measure numbers + HScrollbar *scroll; + ScoreViewBase(Rect rect,int typ): + draw_mode(eColorValue), + play_start(0),play_stop(-1), + leftside(0), + piano_y_off(y_off), + prev_drawmode(0), + sect_len(sect_length), + scale_dep(eScDep_no), + key(0), + zoomed(false), + keep_m_action(false), + sv_type(typ), + score(0), + tnview(0) { + static bool ds[12]={ 1,0,1,0,1,1,0,1,0,1,0,1 }; + p_set_scline_col(ds,false,false); + } + void mouseDown_mnr(int x,int y,int button); + int sectnr(int x) { + return (x+leftside*sect_len)/sect_len; + } + void p_set_scline_col(bool *b,bool gt_12,bool shift_12) { // gt_12: chord>7 ? + if (gt_12) + for (int i=0;i<24;++i) { + if (shift_12) + p_scline_col[23-i]= b[i] ? i==0 ? cBlue : i>12 ? cGreen : cGrey : 0; + else + p_scline_col[23-i]= b[(i+12)%24] ? i==12 ? cBlue : i>12 ? cGrey : cGreen : 0; + } + else + for (int i=0;i<12;++i) + p_scline_col[11-i]=p_scline_col[23-i]= b[i] ? (i==0||i==12 ? cBlue : cGrey) : 0; + } + void draw_sc2(bool clear,int delta=0); + void draw_start_stop(Score *score); + void enter_start_stop(Score *score,int snr,int mouse_but); + virtual void draw_endline(bool draw_it); + void draw_meter_nrs(Score*); + void draw_scview(int start_snr,int stop_snr,int meter); + void p_draw_scview(int start_snr,int stop_snr,int meter); + uint p_lnr2col(int lnr,int sign) { + static int nr0=lnr_to_midinr(0,0); + int diff= scale_dep==eScDep_lk ? keynr2ind(app->chordsWin->the_key_nr) : + scale_dep==eScDep_sk ? keynr2ind(score->sc_key_nr) : + 0; + return p_scline_col[(nr0 - lnr_to_midinr(lnr,sign) + diff)%24]; + } + void draw_info(ScSection *sec,bool is_mus) { + const char *const acc_name[3]= { "","sign=sharp","sign=flat" }; + char buffer[150], + *p; + alert("note info:"); + for(;sec;sec=sec->nxt()) { + p=buffer; + p+=sprintf(p," color=%s #90group=%d ",color_name[sec->s_col],sec->s_group); + if (sec->sampled) { + p+=sprintf(p,"#140(sampled)#200%s ",acc_name[sec->sign]); + } + else if (sec->stacc) + p+=sprintf(p,"#140%s #210stacc ",acc_name[sec->sign]); + else + p+=sprintf(p,"#140%s ",acc_name[sec->sign]); + if (is_mus) + sprintf(p,"#250src=%s",sec->src_tune < tn_max-1 ? tnames.txt(sec->src_tune) : "?"); + alert(buffer); + } + } +}; + +int ypos(int lnr) { // linenr -> y + return y_off + sclin_dist * lnr; +} + +int ypos(int lnr,int sign,int y_offset) { // supposed: draw_mode = ePiano + static int diff=lnr_to_midinr(0,0); + return (diff-lnr_to_midinr(lnr,sign)) * p_sclin_dist + y_offset; +} + +struct TunesView { + VScrollbar *scroll; + RButWin *rbutwin; + TunesView(Rect rect) { + rect.width-=scrollbar_wid; + rbutwin=new RButWin(top_win,rect,MR,"tunes",false,tune_cmd,cBackground); + Score* sc; + for (int n=0;n<=scores.lst_score;++n) { + sc=scores[n]; + sc->rbut=rbutwin->add_rbut(tnames.txt(sc->name)); + } + if (scores.lst_score>=0) + app->act_tune=scores[0]; + scroll=new VScrollbar(top_win->win,Rect(rect.x+rect.width,rect.y,0,rect.height), + MR,(scores.lst_score+2)*TDIST,scr_cmd); + } + static void tune_cmd(Id,int nr,int fire); + static void scr_cmd(Id id,int val,int,bool); + void set_scroll_range() { scroll->set_range((scores.lst_score+2)*TDIST); } +}; + +struct MeterView { + HSlider *meter; + static const int meter_dim=6; + Array m; + MeterView(Rect rect) { + m=(int[meter_dim]){ 6,8,12,16,24,32 }; + meter=new HSlider(top_win,rect,MR,0,meter_dim-1,"meter",0,0,meter_cmd,cBackground); + draw(app->act_meter); + } + void draw(int val) { + int i=m.get_index(val); + if (i>=0) { + set_text(meter->text,"%d",val); + meter->set_hsval(i,0); + return; + } + alert("warning: non-standard meter value %d",val); + set_text(meter->text,"%d",val); + meter->set_hsval(-1,0); + } + static void meter_cmd(Id,int val,int,char*&,bool); +}; + +struct TempoView { + HSlider *tempo; + TempoView(Rect rect) { + tempo=new HSlider(top_win,rect,MR,6,16,"tempo",0,0,tempo_cmd,cBackground); + tempo->value()=11; + set_text(tempo->text,"110"); + } + static void tempo_cmd(Id,int val,int,char *&txt,bool) { + app->act_tempo=val; + set_text(txt,"%d",10*val); + } +}; + +uint col2color(int col) { + switch (col) { + case eBlack: return cBlack; + case ePurple: return cPurple; + case eRed: return cRed; + case eBlue: return cBlue; + case eGreen: return cGreen; + case eBrown: return cBrown; + case eGrey: return cGrey; + default: + alert("col2color: %d?",col); + return cBlack; + } +} + +XftColor *xft_col2color(int col) { + switch (col) { + case eBlack: return xft_Black; + case ePurple: return xft_Purple; + case eRed: return xft_Red; + case eBlue: return xft_Blue; + case eGreen: return xft_Green; + case eBrown: return xft_Brown; + case eGrey: return xft_Grey; + default: + alert("xft_col2color: %d?",col); + return xft_Black; + } +} + +PhmCtrl *col2phm_ctrl(int col) { + switch (col) { + case eBlack: return app->black_phm_control; + case eRed: return app->red_phm_control; + case eGreen: return app->green_phm_control; + case eBlue: return app->blue_phm_control; + case eBrown: return app->brown_phm_control; + case ePurple: return app->purple_phm_control; + default: alert("col2phm_ctrl: col=%d",col); return app->black_phm_control; + } +} + +CtrlBase *col2ctrl(int col) { + switch (col) { + case eBlack: return app->black_control; + case eRed: return app->red_control; + case eGreen: return app->green_control; + case eBlue: return app->blue_control; + case eBrown: return app->brown_control; + case ePurple: return app->purple_control; + default: alert("col2ctrl: col=%d",col); return app->black_control; + } +} + +FMCtrl *col2FMctrl(int col) { + switch (col) { + case eBlack: return app->black_control; + case eBrown: return app->brown_control; + default: alert("col2FMctrl: col=%d",col); return app->black_control; + } +} + +struct ColorView { + RButWin *rbutwin; + ColorView(Rect rect) { + rbutwin=new RButWin(top_win,rect,MR,"colors",false,color_cmd,cBackground); + for (int n=0;nadd_rbut(color_name[n],xft_col2color(n)); + } + static void color_cmd(Id id,int nr,int fire); +}; + +struct Question { + BgrWin *bgwin; + Question(Rect rect) { + bgwin=new BgrWin(top_win->win,rect,MR,0,cForeground); + new Button(bgwin->win,Rect(2,TDIST,20,0),FN,"ok",button_cmd,Id('ok')); + app->dia_wd=new DialogWin(bgwin->win,Rect(24,TDIST,rect.width-28,0),FN,true,cForeground); + set_custom_cursor(app->dia_wd->win,text_cursor); + app->dia_wd->show_menu=false; + } +}; + +void arrow_up(uint win,XftDraw*,Id id,int,int) { + const int x=8,y=10; + static Point pts_u[3]={ Point(x,y-7),Point(x-4,y),Point(x+4,y) }; + fill_polygon(win,cBlack,pts_u,3); +} + +void arrow_down(uint win,XftDraw*,Id id,int,int) { + const int x=8,y=10; + static Point pts_d[3]={ Point(x,y),Point(x-4,y-6),Point(x+4,y-6) }; + fill_polygon(win,cBlack,pts_d,3); +} + +void cross_sign(uint win,XftDraw*,Id id,int,int) { + const int x=5,y=4; + draw_line(win,2,cBlack,Point(x,y),Point(x+7,y+6)); + draw_line(win,2,cBlack,Point(x,y+6),Point(x+7,y)); +} + +struct MouseAction { + ExtRButCtrl *ctrl; + RExtButton *but_all, + *but_all_col, + *but_move, + *but_copy, + *but_select, + *but_multi, + *but_ninf, + *but_porta, + *but_flat,*but_sharp,*but_normal; + VSlider *semi_tones; + Button *unselect; + MouseAction() { + ctrl=new ExtRButCtrl(exp_rbutton_cmd); + Rect rect2(2,4,42,0); + but_select=ctrl->add_extrbut(top_win->win,rect2,FN,Label("select",0),Id('sel')); + rect2.y+=but_dist; rect2.width=55; + ctrl->add_extrbut(top_win->win,rect2,FN,"sel color",Id('scol')); + rect2.y+=but_dist; + but_all=ctrl->add_extrbut(top_win->win,rect2,FN,Label("sel all \273",4),Id('all')); + rect2.y+=but_dist; + but_all_col=ctrl->add_extrbut(top_win->win,rect2,FN,"sel col \273",Id('allc')); + rect2.y+=but_dist; rect2.width=42; + but_move=ctrl->add_extrbut(top_win->win,rect2,FN,Label("move",0),Id('move')); + rect2.y+=but_dist; + but_copy=ctrl->add_extrbut(top_win->win,rect2,FN,Label("copy",0),Id('copy')); + rect2.y+=but_dist; rect2.width=55; + but_porta=ctrl->add_extrbut(top_win->win,rect2,FN,Label("portando",0),Id('prta')); + rect2.y+=but_dist; + but_multi=ctrl->add_extrbut(top_win->win,rect2,FN,Label("multinote",5),Id('muln')); + rect2.y+=but_dist; rect2.width=42; + but_sharp=ctrl->add_extrbut(top_win->win,rect2,FN,"sharp",Id('up')); + rect2.y+=but_dist; + but_flat=ctrl->add_extrbut(top_win->win,rect2,FN,"flat",Id('do')); + rect2.y+=but_dist; rect2.width=45; + but_normal=ctrl->add_extrbut(top_win->win,rect2,FN,"normal",Id('left')); + rect2.y+=but_dist; rect2.width=55; + but_ninf=ctrl->add_extrbut(top_win->win,rect2,FN,Label("note info",5),Id('ninf')); + rect2.y+=but_dist; + unselect=new Button(top_win->win,rect2,FN,"unselect",button_cmd,Id('uns')); + rect2.y+=but_dist; + new Button(top_win->win,rect2,FN,"re-color",button_cmd,Id('rcol')); + rect2.y+=but_dist; rect2.width=42; + new Button(top_win->win,rect2,FN,"delete",button_cmd,Id('del ')); + rect2.y+=but_dist+20; rect2.width=55; + semi_tones=new VSlider(top_win,Rect(2,rect2.y,55,74),FN,1,12,"shift/cp"," 1","12",slider_cmd,cBackground,Id('semt')); + // 74 = 11 * 6 + 8 + semi_tones->value=6; + set_text(semi_tones->text," 6 semi t."); + int y1=rect2.y+78; + button_style.set(1,cBackground,0); + new Button(top_win->win,Rect(1,y1,14,0),FN,arrow_up,button_cmd,Id('shmu')); // shift move up + new Button(top_win->win,Rect(15,y1,40,0),FN,Label("shift",arrow_down),button_cmd,Id('shmd'));// shift move down + y1+=but_dist; + new Button(top_win->win,Rect(1,y1,14,0),FN,arrow_up,button_cmd,Id('shcu')); // copy move up + new Button(top_win->win,Rect(15,y1,40,0),FN,Label("copy",arrow_down),button_cmd,Id('shcd')); // copy move down + button_style=def_but_st; + } + void reset() { + ctrl->reset(); + app->act_action=0; + } +}; + +struct EditScript { + EditWin *textview, + *meas_info; + VScrollbar *scroll; + EditScript(Rect rect) { + meas_info=new EditWin( + top_win->win,Rect(rect.x,rect.y,26,rect.height),FN,false,0); + scroll=new VScrollbar( + top_win->win,Rect(rect.x+27,rect.y,scrollbar_wid,rect.height),FN,rect.height,scr_cmd); + textview=new EditWin( + top_win->win,Rect(rect.x+scrollbar_wid+28,rect.y,rect.width-scrollbar_wid-28,rect.height),FR,true,key_cmd); + set_custom_cursor(textview->win,text_cursor); + } + static void key_cmd(Id,int ctrl_key,int key); + static void scr_cmd(Id,int yoff,int range,bool); + void write_scriptf(FILE*); + void save_scriptf(FILE*); + void print_meas(int pos,int snr) { + Str str; + meas_info->set_line(str.tos(snr/app->act_meter),pos); + } + void reset_sbar() { + int nr_lines; + textview->get_info(&nr_lines,0,0); + scroll->value=0; + scroll->set_range((nr_lines+4)*TDIST); + meas_info->set_y_off(0); + } +}; + +const char + *maj_min_keys[keys_max*4] = { + "C","Db","D","Eb","E","F","F#","Gb","G","Ab","A","Bb","B", // preferred + "a","bb","b","c","c#","d","d#","eb","e","f","f#","g","g#", + "C","Des","D","Es","E","F","Fis","Ges","G","As","A","Bes","B", // classic + "a","bes","b","c","cis","d","dis","es","e","f","fis","g","gis" + }; + + +struct ReverbView { + uint sel_col; + BgrWin *bgwin; + HVSlider *reverb; + ReverbView(Rect rect):sel_col(eGreen) { + bgwin=new BgrWin(top_win->win,rect,MR,draw,cForeground); + + reverb=new HVSlider(bgwin,Rect(4,TDIST,66,38),12,FN,Int2(0,0),Int2(4,2), + "reverb/ph","0","4","0","2",hvslider_cmd,cForeground,Id('rval')); + reverb->value.set(0,0); + + button_style.set(0,cForeground,1); + new Button(bgwin->win,Rect(5,51,38,0),FN,"instr?",button_cmd,Id('revc')); + button_style=def_but_st; + } + static void draw(Id); +}; + +struct Menus { + BgrWin *bgwin; + CmdMenu *file, + *imp_export, + *help; + Menus(Rect rect) { + bgwin=new BgrWin(top_win->win,rect,MR,0,cForeground); + file=new CmdMenu(bgwin->win,Rect(6,3,0,0),FN,fill_file_menu,"File",cForeground); + imp_export=new CmdMenu(bgwin->win,Rect(68,3,0,0),FN,fill_imp_exp_menu,"Import/Export",cForeground); + help=new CmdMenu(bgwin->win,Rect(172,3,0,0),FN,fill_help_menu,"Help",cForeground); + } + static void fill_file_menu(), + fill_imp_exp_menu(), + fill_help_menu(); +}; + +struct AppLocal { + ArrayscViews; + Menus *menus; + MusicView *musicView; + EditScript *editScript; + TunesView *tunesView; + MeterView *meterView; + TempoView *tempoView; + ColorView *colorView; + Question *textView; + MouseAction *mouseAction; + struct InfoView *info_view; + ReverbView *revb_view; + HVSlider *adj; + RButWin *mv_display; + static void mv_display_cmd(Id id,int nr,int fire); + AppLocal(); + static void remove_cmd(Id); + static void clear_cmd(Id); + static void modt_cmd(Id); + static void run_script_cmd(Id); + static void mplay_cmd(Id) { + if (i_am_playing) return; + app->mplay(); + } + static void stop_cmd(Id) { + app->stop_requested=true; + i_am_playing=false; // to terminate lockup-state + if (mk_connected && kb_tune.cur_ind>=0) { + Score *sc=new_score("keyboard"); + sc->copy_keyb_tune(); + kb_tune.reset(); + } + } + static void mvup_cmd(Id) { + int ind=app->act_tune_ind; + if (!app->act_tune || ind==0) return; + app_local->tunesView->rbutwin->set_rbut(scores.buf[ind-1]->rbut,false); + scores.swap(ind,ind-1); + --app->act_tune_ind; + } + static void mvdo_cmd(Id) { + int ind=app->act_tune_ind; + if (!app->act_tune || ind==scores.lst_score) return; + app_local->tunesView->rbutwin->set_rbut(scores.buf[ind+1]->rbut,false); + scores.swap(ind,ind+1); + ++app->act_tune_ind; + } + static void chords_cmd(Id) { + if (app->chordsWin) map_window(app->chordsWin->chwin); + else app->chordsWin=new ChordsWindow(Point(700,20)); + } + static void raw_cmd(Id id,bool val) { + if (val && app->act_instr_ctrl->synth) hide_window(app->act_instr_ctrl->synth->topwin); + app->act_instr_ctrl->cview->hide(); + app->act_instr_ctrl=val ? col2phm_ctrl(app->act_color) : col2ctrl(app->act_color); + app->act_instr_ctrl->cview->map(); + } + static void con_mk_cmd(Id id,bool val) { + if (val) { + switch (midi_input_mode) { + case eMidiDev: + pthread_create(&thread2,0,conn_mk_alsa,0); break; + case eMidiJack: + pthread_create(&thread2,0,conn_mk_jack,0); break; + default: + alert("midi input mode?"); + mk_connected=false; + } + kb_tune.reset(); + } + else + mk_connected=false; // this will stop start_conn_mk() + } +}; + +struct Selected { + struct ScoreView *sv; // the selected scoreview + SLinkedList sd_list; + bool inv; // list inverted? + Selected():sv(0),inv(false) { } + void insert(int lnr,int snr,int sign) { + if (!sd_list.lis) { + static Button *us=app_local->mouseAction->unselect; + us->xft_text_col=xft_Red; + us->draw_button(); + } + sd_list.insert(SectData(lnr,snr,sign),!inv); + } + void remove(int lnr,int snr) { + sd_list.remove(SectData(lnr,snr,0)); + if (!sd_list.lis) { + static Button *us=app_local->mouseAction->unselect; + us->xft_text_col=xft_Black; + us->draw_button(); + } + } + void check_direction(int delta_lnr,int delta_snr) { + if ((!inv && (delta_lnr>0 || delta_snr>0)) || + (inv && (delta_lnr<0 || delta_snr<0))) { + sd_list.invert(); + inv=!inv; + } + } + void reset() { + sd_list.reset(); + inv=false; + if (app_local) { // is 0 at startup + Button *us=app_local->mouseAction->unselect; + us->xft_text_col=xft_Black; + us->draw_button(); + } + } + void restore_sel(); + int min_snr() { + SLList_elem *sd; + int snr=sd_list.lis->d.snr; + for (sd=sd_list.lis;sd;sd=sd->nxt) + if (snr>sd->d.snr) snr=sd->d.snr; + return snr; + } +} selected; + +struct InfoView { + BgrWin *bgwin; + TextWin *txt[4]; + Lamp *lamps[3]; + static const int dist=17; + + InfoView(Rect rect) { + bgwin=new BgrWin(top_win->win,rect,FN,draw_cmd,cBackground,0); + txt[eSco]=new TextWin(bgwin->win,Rect(62,dist+1,110,0),FN,1); // .sco file + txt[eScr]=new TextWin(bgwin->win,Rect(62,dist*2+1,110,0),FN,1); // .scr file + txt[eMnr]=new TextWin(bgwin->win,Rect(62,dist*3+1,35,0),FN,1); // measure nr + txt[eCdir]=new TextWin(bgwin->win,Rect(62,1,rect.width-64,0),FN,1); // current dir + draw_cwd(false); + lamps[eSco]=new Lamp(bgwin->win,Rect(177,dist+3,0,0),cForeground); + lamps[eScr]=new Lamp(bgwin->win,Rect(177,dist*2+3,0,0),cForeground); + lamps[eClip]=new Lamp(bgwin->win,Rect(165,dist*3+3,0,0),cForeground); + for (int i=0;i<3;++i) lamps[i]->col=cLightGreen; + } + void set_modif(int nr,bool on) { + uint col=on ? cRed : cLightGreen; + lamps[nr]->lamp_color(col); + } + void draw() { + bgwin->clear(); + xft_draw_string(bgwin->xft_win,xft_Black,Point(2,12),"current dir:"); + xft_draw_string(bgwin->xft_win,xft_Black,Point(2,12+dist),"score file:"); + xft_draw_string(bgwin->xft_win,xft_Black,Point(2,12+dist*2),"script file:"); + xft_draw_string(bgwin->xft_win,xft_Black,Point(2,12+dist*3),"measure:"); + xft_draw_string(bgwin->xft_win,xft_Black,Point(110,12+dist*3),"clipping?"); + for (int i=0;i<3;++i) lamps[i]->draw(); + } + static void draw_cmd(Id) { app_local->info_view->draw(); } + void draw_cwd(bool do_draw) { + char buf[TextWin::SMAX]; + if (!getcwd(buf,TextWin::SMAX)) { alert("getcwd problem"); return; } + char *start=buf+max(0,strlen(buf)-26); // long strings: skip begin + if (do_draw) txt[eCdir]->print_text(start); + else txt[eCdir]->add_text(start); + } +}; + +char *cconst2s(int cc) { // char constant -> string + static char buf[5]; + char ch; + int i; + for (i=0;i<4;++i) { + ch=(cc/(1 << 8*(3-i))) & 0xff; + buf[i]=ch; + } + for (i=0;i<4 && !buf[i];++i); + return buf+i; +} + +void handle_uev(int cmd,int param,int param2) { + switch (cmd) { + case 'scop': + if (app->act_scope_scale!=app->scopeView->scale->value) + app->scopeView->set_scale(); // sets app->act_scope_scale + app->scopeView->draw(); + break; + case 'repm': + app->report_meas(param); + break; + case 'clip': + app_local->info_view->set_modif(eClip,true); + break; + case 'wfd': // wfile_play() finished + wf_playing=false; + break; + case 'done': // play() finished + i_am_playing=false; + if (app->cur_score->sc_type==eMusic) { + if (app->task==eDumpwav) { + if (!close_dump_wav()) + alert("wave file closing problem"); + else + app->dia_wd->dlabel("wave file created",cForeground); + app->task=0; + } + } + else if (app->repeat && !app->stop_requested) { + kb_tune.nxt_turn(); + app->svplay(0); + } + break; + case 'vcfd': + case 'clvd': + monosynth_uev(cmd,param); // monosynth display command + break; + case 'sisa': + col2ctrl(param)->draw_isa_col(); + break; + case 'msis': + col2ctrl(param)->synth->draw_isa_col(true); + break; + case 'dr0': { + static HSlider *hsl=app_local->tempoView->tempo; + set_text(hsl->text,"%d",10*app->act_tempo); + hsl->set_hsval(app->act_tempo,0); + break; + } + case 'dr1': app->red_control->start_timbre->draw(); break; + case 'dr2': app->red_control->timbre->draw(); break; + case 'dr3': app->red_control->startup->draw(); break; + case 'dr4': app->red_control->start_amp->draw(); break; + case 'dr5': app->red_control->decay->draw(); break; + case 'dr6': app->purple_control->st_harm[param]->draw(); break; + case 'dr7': app->purple_control->harm[param]->draw(); break; + case 'dr8': app->purple_control->start_dur->draw(); break; + case 'dr9': app->blue_control->attack->draw(); break; + case 'dr10': app->blue_control->decay->draw(); break; + case 'dr11': app->blue_control->rich->draw(); break; + case 'dr12': app->blue_control->chorus->draw(); break; + case 'dr13': app->black_control->fm_ctrl->draw(); break; + case 'dr14': app->black_control->attack->draw(); break; + case 'dr15': app->black_control->decay->draw(); break; + case 'dr16': app->black_control->detune->draw(); break; + case 'dr17': app->brown_control->fm_ctrl->draw(); break; + case 'dr18': app->brown_control->detune->draw(); break; + case 'dr19': app->brown_control->attack->draw(); break; + case 'dr20': app->brown_control->decay->draw(); break; + case 'dr21': app->blue_control->dur_limit->draw(); break; + case 'dr22': app->red_control->dur_limit->draw(); break; + case 'dr23': app->green_control->freq_mult->draw(); break; + case 'dr24': app->green_control->attack->draw(); break; + case 'dr25': app->green_control->decay->draw(); break; + case 'dr26': app->green_control->chorus->draw(); break; + case 'dr27': app->blue_control->lowpass->draw(); break; + case 'dr33': col2ctrl(param)->mode->set_rbutnr(1,false); break; + case 'dr36': col2ctrl(param)->mode->set_rbutnr(param2,false); break; + case 'dr40': + app_local->revb_view->reverb->draw(); + ReverbView::draw(0); + break; + case 'dr41': app->purple_control->sound->draw_actb(); break; + case 'dr43': app->purple_control->decay->draw(); break; + case 'dr44': app->black_control->mod_mod->draw(); break; + case 'dr45': app->brown_control->mod_mod->draw(); break; + case 'dr46': { + PhmCtrl *pct=col2phm_ctrl(param); + if (!pct->reparent()) { + pct->wave_ampl->draw(); + pct->file_select->draw(); + } + } + break; + case 'dr49': col2phm_ctrl(param)->wave_ampl->draw(); break; + case 'dr50': col2phm_ctrl(param)->ctrl_pitch->draw(); break; + case 'dr51': col2FMctrl(param)->base_freq->draw(); break; + case 'dr52': col2ctrl(param)->ampl->draw(); break; + case 'dr55': app->red_control->tone->draw_actb(); break; + case 'dr58': { + PhmCtrl *pmctr=col2phm_ctrl(param); + if (!pmctr->reparent()) { + pmctr->speed_tension->draw(); + pmctr->decay->draw(); + pmctr->wave_ampl->draw(); + pmctr->add_noise->draw(); + } + } + break; + case 'dr64': app->green_control->timbre1->draw(); break; + case 'dr65': app->green_control->timbre2->draw(); break; + case 'dr66': col2ctrl(param)->synth->hsliders[param2]->draw(); break; + case 'dr67': col2ctrl(param)->synth->rbutwins[param2]->draw_actb(); break; + case 'dr68': col2ctrl(param)->synth->checkboxs[param2]->draw(); break; + case 'dr69': + col2ctrl(param)->synth->draw_eg_display(1); + col2ctrl(param)->synth->draw_eg_display(2); + break; + case 'dr70': col2ctrl(param)->synth->draw_wave_display(1); break; + case 'dr71': col2ctrl(param)->synth->draw_wave_display(2); break; + case 'dr72': col2ctrl(param)->synth->draw_wave_display(3); break; + case 'dr73': col2ctrl(param)->synth->draw_wave_display(4); break; + default: + alert("unk client message '%s'",cconst2s(cmd)); + } +} + +void stop_conn_mk() { app->conn_mk->set_cbval(false,0); } + +struct ScoreView:ScoreViewBase { + int index, + state, // set when mouse down + cur_lnr, cur_snr, // set when mouse down + prev_snr, // previous visited section nr + delta_lnr, delta_snr, + left_snr; // new snr of leftmost selected section + char the_chord_name[50]; + ScoreView *other; + BgrWin *text_win, + *signsview, + *chord_name; + TextWin *sc_name; + CheckBox *zoom, + *set_repeat, + *play_1col; + RExtButton *active; + RButWin *display_mode, + *group_nr; + Point cur_point, // set when mouse down + prev_point; // set when mouse moved + + ScoreView(Rect rect,int ident); + static void draw_cmd(Id id); + static void drawsigns_cmd(Id id); + void mouseDown(int x,int y,int button); + void mouseMoved(int x,int y,int button); + void mouseUp(int x,int y,int button); + void select_all(int start); + void select_all_1col(int start); + void modify_sel(int mes); + void sel_column(int snr); + void sel_column_1col(int snr,uint col,bool sampled); + void assign_score(Score*,bool draw_it); + void draw_endline(bool); + void drawSigns(); + void draw_chordn(bool store_name); + static void draw_chn(Id id) { + the_sv(id.id2)->draw_chordn(false); + } + int linenr(int y,int snr,uchar& sign) { + if (draw_mode==ePiano) { + uchar lnr; + static int diff=lnr_to_midinr(0,0); + uchar midinr=diff - (y + ptr_dy - piano_y_off) / p_sclin_dist; + if (!midinr_to_lnr(midinr,lnr,sign,score->signs_mode)) return -1; + ScSection *sec=score->get_section(lnr,snr); + if (sec->cat==eSilent) { // search for ePlay section with same midinr + if (lnr>0 && (sec=score->get_section(lnr-1,snr))->cat==ePlay && + lnr_to_midinr(lnr-1,sec->sign)==midinr) { + --lnr; + sign=sec->sign; + } + else if (lnrget_section(lnr+1,snr))->cat==ePlay && + lnr_to_midinr(lnr+1,sec->sign)==midinr) { + ++lnr; + sign=sec->sign; + } + } + return lnr; + } + return (y-y_off+ptr_dy)/sclin_dist; + } + static void svplay_cmd(Id id) { + if (i_am_playing) return; + app->svplay(the_sv(id.id2)); + } + static void zoom_cmd(Id id,bool val) { + ScoreView *sv=the_sv(id.id2); + if (sv->zoomed!=val) { + if (val) { + sv->sect_len=sect_length*subdiv; + int maxl=sect_scv/subdiv; + if (sv->leftside > sv->score->len-maxl) sv->leftside=sv->score->len-maxl; + } + else { + sv->sect_len=sect_length; + if (sv->score->len<=sect_scv) sv->leftside=0; + else sv->leftside/=subdiv; + } + sv->zoomed=val; + sv->scroll->value=sv->leftside*sv->sect_len; + sv->set_scroll_range(); + sv->draw_sc2(true); + } + } + static void active_cmd(Id id,bool val) { + if (val) { + app->act_score=id.id2; + ScoreView *sv=the_sv(app->act_score); + if (sv->score) { + int m=sv->score->sc_meter; + if (!m) m=app->act_meter; + app_local->meterView->draw(m); + } + } + else { + app->act_score=nop; + app_local->meterView->draw(app->act_meter); + } + } + static void display_cmd(Id id,int nr,int fire) { + ScoreView *sv=the_sv(id.id2); + switch (nr) { + case 0: sv->draw_mode=eColorValue; break; + case 1: sv->draw_mode=eTimingValue; break; + case 2: sv->draw_mode=eAccValue; break; + case 3: + if (sv->prev_drawmode==ePiano) { + if (sv->piano_y_off==y_off) sv->piano_y_off=p_y_off; + else sv->piano_y_off=y_off; + } + sv->draw_mode=ePiano; + break; + } + sv->prev_drawmode=sv->draw_mode; + sv->draw_sc2(true); + } + static void scroll_cmd(Id id,int xoff,int range,bool repeat_on) { + ScoreView *sv=the_sv(id.id2); + sv->scroll->style.param=sv->sect_len; // step size if repeat_on + int ls=xoff/sv->sect_len; + if (repeat_on || ls!=sv->leftside) { + int delta=sv->leftside-ls; + sv->scview->move_contents_h(delta*sv->sect_len); + sv->leftside=ls; + sv->draw_sc2(false,delta); + } + } + void set_scroll_range() { if (score) scroll->set_range(score->len*sect_len+10); } + void reset() { + if (score && score==other->score) { + score=0; + other->reset(); + } + else score=0; + scview->clear(); + sc_name->print_text(""); + chord_name->hide(); + } +}; + +struct MusicView:ScoreViewBase { + MusicView(Rect rect); + static void draw_cmd(Id); + void draw_tune_name(int snr); + void draw_tune_names(); + void exec_info_t0(); + void reset(bool reset_start,bool reset_len,bool reset_scroll) { + score->reset(reset_len); + if (reset_start) { play_start=0; play_stop=-1; } + if (reset_scroll) { leftside=0; scroll->value=0; } + mn_buf.reset(eMusic); + scview->clear(); + tnview->clear(); + mnrview->clear(); + phm_buf.reset(); + wave_buf.reset_buffers(); + } + void upd_info(int tim,ScInfo& sci) { + if (tim) score->check_len(tim+1); + score->scInfo[tim].add(sci); + } + static void scroll_cmd(Id,int xoff,int range,bool repeat_on) { + MusicView *mv=the_mv(); + int ls=xoff/mv->sect_len; + if (repeat_on || ls!=mv->leftside) { + int delta=mv->leftside-ls; + mv->scview->move_contents_h(delta*mv->sect_len); + mv->leftside=ls; + mv->draw_sc2(false,delta); + } + } + void mouseDown(int x,int y,int button); + void mouseUp(int x,int y,int button); + void set_scroll_range() { scroll->set_range(score->end_sect*sect_len+10); } +}; + +struct LineColor { + uint col[sclin_max]; + void init() { + int i; + for (i=0;iinfo_view->draw_cwd(true); + } +} + +static int cmp_labels(const void* a,const void* b) { + return strcmp(reinterpret_cast(a)->label, + reinterpret_cast(b)->label); +} + +void fill_menu(int cmd_id) { // fill dialog menu + switch (cmd_id) { + case 'sco': + case 'scr': { + DIR *cdir=opendir("."); + dirent *dir; + const char *ext1= cmd_id=='sco' ? ".sco" : ".scr", + *ext=0; + if (!cdir) { alert("files: current dir not opeded"); return; } + while ((dir=readdir(cdir))!=0) { + if (!dir->d_ino) { alert("d_ino?"); continue; } + ext=strrchr(dir->d_name,'.'); + if (!ext || strcmp(ext,ext1)) continue; + app->dia_wd->add_mbut(dir->d_name); + } + closedir(cdir); + } + break; + case 'dir': { + DIR *cdir=opendir("."); + if (!cdir) { alert("dirs: current dir not opeded"); return; } + dirent *dir; + while ((dir=readdir(cdir))!=0) { + if (!dir->d_ino) { alert("d_ino?"); continue; } + if (dir->d_type & DT_DIR && (dir->d_name[0]!='.' || !strcmp(dir->d_name,".."))) // no '.', no hidden files + app->dia_wd->add_mbut(dir->d_name); + } + closedir(cdir); + } + break; + default: alert("fill_menu?"); return; + } + qsort(app->dia_wd->but,app->dia_wd->butnr+1,sizeof(ChMButton),cmp_labels); +} + +struct DialogCommands { + static void save(const char *fname) { + if (app->save(fname)) { + app->dia_wd->dlabel("scorefile saved",cForeground); + app->input_file.cpy(fname); + modify_cwd(app->input_file); + app_local->info_view->txt[eSco]->print_text(app->input_file.s); + app_local->info_view->set_modif(eSco,false); + } + } + static void read_tunes(const char *fname) { + app->input_file.cpy(fname); + if (app->read_tunes(fname,false)) { + app->dia_wd->dlabel("scorefile loaded",cForeground); + modify_cwd(app->input_file); + InfoView *iv=app_local->info_view; + iv->txt[eSco]->print_text(app->input_file.s); + iv->set_modif(eSco,false); + } + } + static void add_tunes(const char *fname) { + if (app->read_tunes(fname,true)) { + app->dia_wd->dlabel("scorefile added",cForeground); + app_local->info_view->set_modif(eSco,true); + } + } + static void new_tune(const char *tname) { + app->new_tune(tname); + app->dia_wd->dlabel("tune added",cForeground); + } + static void copy_tune(const char *tname) { + app->copy_tune(tname); + app->dia_wd->dlabel("copied",cForeground); + } + static void rename_tune(const char *tname) { + ScoreView *sv; + Score *sc=app->act_tune; + if (!sc) return; + tnames.replace(sc->name,tname); + app_local->tunesView->rbutwin->re_label(sc->rbut,tnames.txt(sc->name)); + if (app->find_score(sc,sv)) + sv->sc_name->print_text(tnames.txt(sc->name)); + app->dia_wd->dlabel("tune renamed",cForeground); + app_local->info_view->set_modif(eSco,true); + } + static void read_script_cmd(const char* sname) { + static MusicView *mv=the_mv(); + if (mv->score->lst_sect>0) { + if (app->script_file==sname) mv->reset(false,false,false); + else mv->reset(true,false,false); + mv->draw_sc2(false); + } + app->script_file.cpy(sname); + InfoView *iv=app_local->info_view; + if (!app->input_file.s[0]) { + app->input_file.cpy(sname); + app->input_file.new_ext(".sco"); + if (!app->read_tunes(app->input_file.s,false)) return; + iv->txt[eSco]->print_text(app->input_file.s); + } + if (app->read_script(sname)) { + app->dia_wd->dlabel("read",cForeground); + modify_cwd(app->script_file); + iv->txt[eScr]->print_text(app->script_file.s); + iv->set_modif(eScr,false); + } + else + app_local->info_view->txt[eScr]->print_text(""); + flush_X(); + } + static void save_script(const char* sname) { + app->script_file.cpy(sname); + if (app->save_script(sname)) { + app->dia_wd->dlabel("script saved",cForeground); + modify_cwd(app->script_file); + app_local->info_view->txt[eScr]->print_text(app->script_file.s); + app_local->info_view->set_modif(eScr,false); + } + } + static void save_cmd(Id) { + app->dia_wd->dlabel("save as:",cRose); + if (!app->input_file.s[0]) app->input_file.cpy("my-tune.sco"); + app->dia_wd->ddefault(app->input_file.s,save); + } + static void load_cmd(Id) { + app->dia_wd->dlabel("load scorefile:",cRose); + if (!app->input_file.s[0]) app->input_file.cpy("my-tune.sco"); + app->dia_wd->ddefault(app->input_file.s,read_tunes,true,fill_menu,'sco'); + } + static void add_cmd(Id) { + app->dia_wd->dlabel("add scorefile:",cRose); + app->dia_wd->ddefault(app->input_file.s,add_tunes,true,fill_menu,'sco'); + } + static void new_cmd(Id) { + app->dia_wd->dlabel("new tune:",cRose); + app->dia_wd->ddefault("no-name",new_tune); + } + static void copy_cmd(Id) { + if (!app->act_tune) { + alert("copy: no tune selected"); + return; + } + app->dia_wd->dlabel("copy to tune:",cRose); + app->dia_wd->ddefault("no-name",copy_tune); + } + static void rename_cmd(Id) { + if (!app->act_tune) { + alert("rename: no tune selected"); + return; + } + app->dia_wd->dlabel("rename to:",cRose); + app->dia_wd->ddefault(tnames.txt(app->act_tune->name),rename_tune); + } + static void script_cmd(Id) { + if (!app->script_file.s[0]) { + if (app->input_file.s[0]) { + app->script_file.cpy(app->input_file.s); + app->script_file.new_ext(".scr"); + } + else app->script_file.cpy("my-tune.scr"); + } + app->dia_wd->dlabel("script:",cRose); + app->dia_wd->ddefault(app->script_file.s,read_script_cmd,true,fill_menu,'scr'); + } + static void save_script_cmd(Id) { + if (!app->script_file.s[0]) app->script_file.cpy("my-tune.scr"); + app->dia_wd->dlabel("save script as:",cRose); + app->dia_wd->ddefault(app->script_file.s,save_script); + } + static void set_wd(const char *s) { + if (chdir(s)) + alert("directory '%s' not accessable",s); + else { + app->dia_wd->dlabel("current dir",cForeground); + app_local->info_view->draw_cwd(true); + } + } + static void current_dir_cmd(Id) { + app->dia_wd->dlabel("set current directory:",cRose); + char buf[100]; + if (!getcwd(buf,100)) { alert("getcwd problem"); return; } + app->dia_wd->ddefault(buf,set_wd,true,fill_menu,'dir'); + } + static void cmd(const char* s) { + if (app->run_script(s)) { + app->command.cpy(s); + app->dia_wd->dlabel("cmd done",cForeground); + MusicView *mv=the_mv(); + mv->set_scroll_range(); + mv->draw_sc2(true); + mv->exec_info_t0(); + } + } + static void cmd_cmd(Id) { + app->dia_wd->dlabel("command:",cRose); + app->dia_wd->ddefault(app->command.s,cmd); + } + static void create_wav(const char *wave_file) { + if (init_dump_wav(wave_file)) { + app->task=eDumpwav; + if (SAMPLE_RATE!=44100) { + int old_srate=SAMPLE_RATE; + SAMPLE_RATE=44100; + set_time_scale(); + app->mplay(); + SAMPLE_RATE=old_srate; + set_time_scale(); + } + else + app->mplay(); + } + else alert("%s not opened",wave_file); + } + static void create_midi(const char *midi_file) { + // act_tempo = 12 -> 120 beats/minute -> midi tempo event value = 500000 + if (midi_out.init(midi_file,subdiv,app->nupq,app->act_meter,12*500000/app->act_tempo)) { + app->task=eMidiout; + app->mplay(); + } + } + static void create_ps(const char *ps_file) { + ps_out.abc_or_ps_file=ps_file; + app->task=ePsout; + if (the_mv()->play_start) { + alert("warning: start has been set to 0"); + the_mv()->play_start=0; // else numbering won't be correct + } + if (!strcmp(ps_file+strlen(ps_file)-4,".abc")) // abc instead of postcript output? + app->task=eAbcout; + app->mplay(); + } + static void import_midi(const char *midi_f) { + Str instr_map(midi_f); + instr_map.new_ext(".gm-map"); + app->input_file.cpy(midi_f); app->input_file.new_ext(".sco"); + app->script_file.cpy(midi_f); app->script_file.new_ext(".scr"); + + FILE *gm_map=fopen(instr_map.s,"r"); + char *gm_mapf=0; + if (gm_map) { + midi_in.read_mapf=true; + gm_mapf=instr_map.s; + app->midi_in_file.cpy(midi_f); + fclose(gm_map); + } + else { + midi_in.read_mapf=false; + gm_mapf=instr_map.s; + } + if (midi_in.read_mf(midi_f,gm_mapf)) { + if (midi_in.read_mapf) { + app->dia_wd->dlabel("midi file read",cForeground); + app_local->meterView->draw(app->act_meter); + } + else { + static char mes[50]; + snprintf(mes,50,"edit %s",instr_map.s); + app->dia_wd->dlabel(mes,cLightBlue); + } + } + } +} dial_cmds; + +bool exec_info(int snr,Score *sc,bool upd_sliders) { + if (app->no_set->value) return true; + ScInfo *sci=sc->scInfo; + if (!sci) return true; + for (sci=sci+snr;sci;sci=sci->next) { + if (sci->tag && !app->exec_info_cmd(*sci,upd_sliders)) return false; + } + return true; +} + +struct RewrScript { + const int + start, + stop, + meter; + bool insert_gap; + char in_buf[max200], + out_buf[max200]; + RewrScript(int sta,int sto,int m): + start(sta),stop(sto),meter(m),insert_gap(sto>sta) { } + int read_rwtime(Str &str,int &pos); + void rewr_line(bool &ok); + void rewr_params(Str &str,int &pos,int mode,char *&ob,bool &ok); + void char_cpy(char ch,char *&ob) { + switch (ch) { + case ';': str_cpy(ob,"; "); break; + case ' ': str_cpy(ob," "); break; + //case '#': str_cpy(ob," #"); break; + case '\n': str_cpy(ob,"\n"); break; + default: ob[0]=ch; ob[1]=0; + alert("unexpected char (%c)",ch); + } + } + void str_cpy(char *&ob,const char *s) { + const char *ib; + for (ib=s;*ib;) (ob++)[0]=(ib++)[0]; + } +}; + +int lnr_to_midinr(int lnr,uint sign) { // ScLine -> midi note number + int ind=lnr%7; + // b a g f e d c + static const int ar[7]={ 0,2,4,6,7,9,11 }; + int nr = ar[ind] + (sign==eHi ? -1 : sign==eLo ? 1 : 0) + (lnr-ind)/7*12; + // middle C: amuc: lnr=27, ind=6, nr=11+21/7*12=47 + // midi: 60 + return 107-nr; // 60=107-47 + // lnr=0, sign=0 -> 107 + // lnr=sclin_max=45 -> 29 +} + +bool midinr_to_lnr(uchar mnr,uchar& lnr,uchar& sign,int signs_mode) { + static const int + // c cis d es e f fis g gis a bes b + ar_0[12]={ 0 , 0 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 6 , 6 }, + s_0[12]= { 0 ,eHi, 0 ,eLo, 0 , 0 ,eHi, 0 ,eHi, 0 ,eLo, 0 }, + + // c des d es e f ges g as a bes b + ar_f[12]={ 0 , 1 , 1 , 2 , 2 , 3 , 4 , 4 , 5 , 5 , 6 , 6 }, + s_f[12]= { 0 ,eLo, 0 ,eLo, 0 , 0 ,eLo, 0 ,eLo, 0 ,eLo, 0 }, + + // c cis d dis e f fis g gis a ais b + ar_s[12]={ 0 , 0 , 1 , 1 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 }, + s_s[12]= { 0 ,eHi, 0 ,eHi, 0 , 0 ,eHi, 0 ,eHi, 0 ,eHi, 0 }; + int ind=mnr%12, + lnr2; + const int *ar, *s; + + switch (signs_mode) { + case eLo: ar=ar_f; s=s_f; break; + case eHi: ar=ar_s; s=s_s; break; + case 0: ar=ar_0; s=s_0; break; + default: + ar=ar_0; s=s_0; + alert("midinr_to_lnr: signs_mode = %d",signs_mode); + } + + // middle C: amuc: lnr=27 + // midi: 60, 60/12*7 = 35, 27=62-35 + + lnr2=62 - mnr/12*7 - ar[ind]; + if (lnr2<0 || lnr2>=sclin_max) return false; + lnr=lnr2; + sign=s[ind]; + return true; +} + +const int piano_lines_max= lnr_to_midinr(0,0) - lnr_to_midinr(sclin_max-1,eLo); + +// ========== specifications =========== + +void Menus::fill_file_menu() { + CmdMenu *file=app_local->menus->file; + file->add_mbut("load scorefile...",dial_cmds.load_cmd); + file->add_mbut("add scorefile...",dial_cmds.add_cmd); + file->add_mbut("save scorefile...",dial_cmds.save_cmd); + file->add_mbut("read script...",dial_cmds.script_cmd); + file->add_mbut("save script...",dial_cmds.save_script_cmd); + file->add_mbut("set current dir...",dial_cmds.current_dir_cmd); +} + +void Menus::fill_imp_exp_menu() { + CmdMenu *imp_exp=app_local->menus->imp_export; + imp_exp->add_mbut("save as WAVE...",menu_cmd,'mkwa'); + imp_exp->add_mbut("save as MIDI...",menu_cmd,'mkmi'); + imp_exp->add_mbut("save as postscript...",menu_cmd,'mkps'); + imp_exp->add_mbut("read MIDI file...",menu_cmd,'imid'); +} + +void Menus::fill_help_menu() { + CmdMenu *help=app_local->menus->help; + help->add_mbut("manual",menu_cmd,'man'); + help->add_mbut("about",menu_cmd,'abou'); +} + +void ReverbView::draw(Id) { + ReverbView *rv=app_local->revb_view; + set_width_color(2,col2color(rv->sel_col)); + rv->bgwin->draw_line(Point(1,0),Point(1,rv->bgwin->dy)); +} + +void Score::tone2signs(int key_nr) { // C = 0, B = 12 + for (int lnr=0;lnract_meter; +} + +ScoreView *the_sv(int i) { return app_local->scViews[i]; } +MusicView *the_mv() { return app_local->musicView; } + +void drawS_ghost(ScoreViewBase* theV,int snr,int lnr,int sign,bool erase) { + int x=(snr-theV->leftside)*theV->sect_len, + y= theV->draw_mode==ePiano? ypos(lnr,sign,theV->piano_y_off) : ypos(lnr); + Point start(x,y), + end(x+theV->sect_len,y); + if (erase) + set_width_color(3,cWhite); + else + set_width_color(3,cDarkGrey); + theV->scview->draw_line(start,end); + if (erase) + theV->score->get_section(lnr,snr)->drawSect(theV,snr,lnr); +} + +void PhmCtrl::set_ampl_txt(int val) { + float fval=ampl_mult[val]; + set_text(wave_ampl->text,"%.2f",fval); +} + +void PhmCtrl::reset_wf_sliders(bool do_draw) { + wave_ampl->value()=3; + set_ampl_txt(3); + if (do_draw) wave_ampl->draw(); +} + +void PhmCtrl::dump_settings(char *buf,int bufmax) { + switch (sample_mode) { + case ePhysmodMode: + snprintf(buf,bufmax,"set %s phm:%d,%d,%d,%d wa:%d cpit:%s", + color_name[ctrl_col],speed_tension->value.x,speed_tension->value.y, + decay->value(),add_noise->value,wave_ampl->value(),ctrl_pitch->value?"on":"off"); + break; + case eWaveMode: + if (wave_buf.fname_nr<0) { + alert("dump settings: wave files not yet read"); + buf[0]=0; + break; + } + if (file_select->value.nr<0) { + alert("dump settings: no wave file for %s instrument",color_name[ctrl_col]); + buf[0]=0; + break; + } + snprintf(buf,bufmax,"set %s wf:%d wa:%d cpit:%s", + color_name[ctrl_col], + wave_buf.filenames[file_select->value.nr].nr, + wave_ampl->value(),ctrl_pitch->value?"on":"off"); + break; + } +} + +bool PhmCtrl::reparent() { + switch (sample_mode) { + case ePhysmodMode: + if (mode->re_parent(subwin1)) { + wave_ampl->re_parent(subwin1); + ctrl_pitch->re_parent(subwin1); + file_select->reset(); + hide_window(subwin2); + map_window(subwin1); + return true; + } + return false; + case eWaveMode: + if (mode->re_parent(subwin2)) { + wave_ampl->re_parent(subwin2); + ctrl_pitch->re_parent(subwin2); + hide_window(subwin1); + map_window(subwin2); + return true; + } + return false; + } + return false; +} + +void dump_ampl(char *buf,SliderData *a_val) { + if (extended) + snprintf(buf,30," a-0:%d a-1:%d a-2:%d",a_val[0].value,a_val[1].value,a_val[2].value); + else + snprintf(buf,30," a:%d",a_val[0].value); +} + +void FMCtrl::dump_settings(char *buf,int bmax) { + const char *col= color_name[ctrl_col]; + if (instrm_val==eSynthMode) + synth->dump_settings(buf,bmax,col); + else { + int n=snprintf(buf,bmax-30,"set %s m:nat fm:%d,%d dt:%d at:%d dc:%d mm:%d,%d bf:%s",col, + fm_ctrl->value.x,fm_ctrl->value.y,detune->value(),attack->value(),decay->value(), + mod_mod->value.x,mod_mod->value.y,base_freq->value?"on":"off"); + dump_ampl(buf+n,ampl_val); + } +} + +void GreenCtrl::dump_settings(char *buf,int bufmax) { + if (instrm_val==eSynthMode) + synth->dump_settings(buf,bufmax,color_name[ctrl_col]); + else { + int n=snprintf(buf,bufmax,"set green m:nat w1:%d,%d w2:%d,%d fr:%d ch:%d at:%d dc:%d", + timbre1->value.x,timbre1->value.y,timbre2->value.x,timbre2->value.y, + freq_mult->value(),chorus->value(), + attack->value(),decay->value()); + dump_ampl(buf+n,ampl_val); + } +} + +void PurpleCtrl::dump_settings(char *buf,int bufmax) { + if (instrm_val==eSynthMode) + synth->dump_settings(buf,bufmax,color_name[ctrl_col]); + else { + int n=snprintf(buf,bufmax,"set purple m:nat sth:%d,%d,%d,%d,%d suh:%d,%d,%d,%d,%d su:%d snd:%d dc:%d", + st_harm[0]->value,st_harm[1]->value,st_harm[2]->value,st_harm[3]->value,st_harm[4]->value, + harm[0]->value,harm[1]->value,harm[2]->value,harm[3]->value,harm[4]->value, + start_dur->value(),sound->act_rbutnr(),decay->value()); + dump_ampl(buf+n,ampl_val); + } +} + +void RedCtrl::dump_settings(char *buf,int bufmax) { + if (instrm_val==eSynthMode) + synth->dump_settings(buf,bufmax,color_name[ctrl_col]); + else { + int n=snprintf(buf,bufmax,"set red m:nat stw:%d,%d suw:%d,%d sa:%d su:%d dc:%d dl:%d tone:%d", + start_timbre->value.x,start_timbre->value.y, + timbre->value.x,timbre->value.y, + start_amp->value,startup->value(),decay->value(),dur_limit->value(),tone->act_rbutnr()); + dump_ampl(buf+n,ampl_val); + } +} + +void BlueCtrl::dump_settings(char *buf,int bufmax) { + if (instrm_val==eSynthMode) + synth->dump_settings(buf,bufmax,color_name[ctrl_col]); + else { + int n=snprintf(buf,bufmax,"set blue m:nat rich:%s ch:%s at:%d dc:%d dl:%d lp:%d", + rich->value?"on":"off",chorus->value?"on":"off", + attack->value(),decay->value(),dur_limit->value(),lowpass->value()); + dump_ampl(buf+n,ampl_val); + } +} + +void EditScript::key_cmd(Id,int ctrl_key,int key) { + app_local->info_view->set_modif(eScr,true); + static EditScript *es=app_local->editScript; + int nr_lines; + if ((ctrl_key==XK_Control_L || ctrl_key==XK_Control_R)) { + if (key=='s') { + int ypos,nrch; + char buf[max200]; + es->textview->get_info(&nr_lines,&ypos,&nrch); + app->act_instr_ctrl->dump_settings(buf,max200); + if (nrch>0) { + es->textview->insert_line(buf,ypos+1); + es->scroll->set_range((nr_lines+4)*TDIST); + } + else es->textview->set_line(buf,ypos); + } + } + else if (key==XK_Return) { + es->textview->get_info(&nr_lines,0,0); + es->scroll->set_range((nr_lines+4)*TDIST); + } +} + +void EditScript::scr_cmd(Id,int yoff,int range,bool) { + EditScript *eds=app_local->editScript; + eds->textview->set_y_off(yoff); + eds->meas_info->set_y_off(yoff); +} + +void App::report_meas(int snr) { + char s[10]; + sprintf(s,"%d",snr/act_meter); + app_local->info_view->txt[eMnr]->print_text(s); +} + +void App::svplay(ScoreView *sv1) { + static ScoreView *sv; + if (sv1) { // sv1 = 0 during repeat + sv=sv1; + scopeView->reset(); + } + app_local->info_view->set_modif(eClip,false); + if (!(cur_score=sv->score)) { + i_am_playing=false; + return; + } + stop_requested=false; + play_1_col= sv->play_1col->value ? act_color : nop; + repeat=sv->set_repeat->value; + if (mk_connected && !repeat) reset_mk_nbuf(); + playScore(sv->play_start,sv->play_stop); +} + +void App::mplay() { + static MusicView *mv=the_mv(); + stop_requested=false; + app_local->info_view->set_modif(eClip,false); + cur_score=mv->score; + scopeView->reset(); + repeat=false; + play_1_col=nop; + playScore(mv->play_start,mv->play_stop); +} + +void AppLocal::run_script_cmd(Id) { + static MusicView *mv=the_mv(); + mv->reset(false,false,false); + static char script_text_buf[2000]; + app_local->editScript->textview->get_text(script_text_buf,2000); + if (app->run_script(script_text_buf)) { + mv->set_scroll_range(); + mv->draw_sc2(false); + mv->exec_info_t0(); + } +} + +ScopeView::ScopeView(Rect rect): + nr_samples(IBsize/8), + ptr(-1), + lst_ptr(0), + time_scale(1), + maxi(scope_dim), + mid(rect.height/2), + round(false) { + for (int i=0;iwin,rect,MR,draw_cmd,0,0,0,cScopeBgr); + bgwin=new BgrWin(top_win->win,Rect(rect.x+rect.width+1,rect.y,38,rect.height),MR,0,cForeground); + scale=new VSlider(bgwin,Rect(2,TDIST,32,52),FN,0,6,"scale","1","64",0,cForeground); + scale->value=1; + set_scale(); +} + +void ScopeView::set_scale() { + int val=app->act_scope_scale=scale->value; + time_scale=1<clear(); +} +void ScopeView::draw_cmd(Id) { app->scopeView->redraw(); } + +void ScopeView::redraw() { + int n,i; + set_width_color(1,cGreen); + if (ptr>=0) + for (n=0,i=ptr;ndraw_lines(pt_buf,maxi); +} +void ScopeView::draw() { // only to be called once, from handle_uev() + int i,n; + set_width_color(1,cGreen); + if (round) { + scopeview->move_contents_h(-nr_samples/time_scale); // 1024/8=128 new samples per 'scop' message + for (n=maxi-scope_dim,i=(ptr+maxi-scope_dim)%maxi;ndraw_lines(pt_buf+maxi-scope_dim,scope_dim); + } + else if (ptr>0) { + for (i=lst_ptr;idraw_lines(pt_buf+lst_ptr,ptr-lst_ptr); + lst_ptr=ptr; + } +} +void ScopeView::insert(int val) { + if (++ptr>=maxi) { round=true; ptr=0; } + buf[ptr]=val; +} +void AppLocal::mv_display_cmd(Id id,int nr,int fire) { + MusicView *mv=the_mv(); + switch (nr) { + case 0: mv->draw_mode=eColorValue; break; + case 1: mv->draw_mode=eTimingValue; break; + case 2: + if (mv->prev_drawmode==ePiano) { + if (mv->piano_y_off==y_off) mv->piano_y_off=p_y_off; + else mv->piano_y_off=y_off; + } + mv->draw_mode=ePiano; + break; + } + mv->prev_drawmode=mv->draw_mode; + mv->draw_sc2(true); +} + +static void set_phm(ScInfo &info,bool upd,int col,int message) { + ShortBuffer *pmd=&info.wavedata; + if (!upd) { + phm_buf.set_phys_model(col,pmd); + } + else { + phm_buf.var_data[col]=pmd; + send_uev(message,col); + } +} + +bool read_wavef(bool upd,ScInfo& info,int message) { + int col=info.idata.col, + filenr=info.idata.d1; + ShortBuffer *wd=wave_buf.w_buf+col; + PhmCtrl *pct=col2phm_ctrl(col); + int butnr=0; + + if (wave_buf.fname_nr<0) { // PhmCtrl::collect_files() not yet run? + if (!wave_buf.sample_dirs->coll_wavefiles()) return false; + } + for (int i=0;;++i) { + if (i>wave_buf.fname_nr) { + if (!upd) alert("read_wavef: wave file nr %d unknown",filenr); + return false; + } + FileName *fn=wave_buf.filenames+i; + if (filenr==fn->nr) { + pct->file_select->cp_value(fn->nr,fn->name); // should be set when upd = true or false + butnr=i; + break; + } + } + pct->reset_wf_sliders(false); + if (upd) { + wd->buf=info.wavedata.buf; + wd->size=info.wavedata.size; + wd->alloced=false; + send_uev(message,col); + } + else { + FileName *fn=wave_buf.filenames+butnr; + if (!read_wav(fn->dir,fn->name,&info.wavedata)) return false; + wd->size=info.wavedata.size; // fill_note_bufs() needs this + wd->buf=0; + } + return true; +} + +void CtrlBase::set_instr_mode(int n,bool upd) { + switch (n) { + case 'i': // obsolete + case 'n': + instrm_val=eNativeMode; + if (upd) send_uev('dr36',ctrl_col,0); + break; + case 'm': + instrm_val=eSynthMode; + if (upd) send_uev('dr36',ctrl_col,1); + break; + } +} + +void CtrlBase::set_ampl_txt(int val) { + set_text(ampl->text,"%.2f",ampl_mult[val]); +} + +void PhmCtrl::set_mode(int tag,ScInfo& info) { + switch (tag) { + case eWaveMode: + sample_mode=tag; + mode->set_rbutnr(1,false,false); + break; + case ePhysmodMode: + sample_mode=tag; + mode->set_rbutnr(0,false,false); + speed_tension->value.set(info.idata.d0,info.idata.d1); + decay->value()=info.idata.d2; + add_noise->value=info.idata.d3; + break; + } +} + +int *CtrlBase::get_ampl_ptr(bool sampled,int group_nr) { + int *ptr=0; + if (sampled) { + ptr=&static_cast(this)->wave_ampl->value(); + } + else { + switch (instrm_val) { + case eNativeMode: + ptr= extended ? &l_val[group_nr].value : &l_val[0].value; + break; + case eSynthMode: + ptr=synth->ms_ampl; + break; + } + } + if (!ptr) { alert("get_ampl?"); static int n=0; return &n; } + return ptr; +} + +bool App::exec_info_cmd(ScInfo& info,bool upd) { + int n; + switch (info.tag) { + case eText: // handled by draw_tune_name() + break; + case eTempo: + act_tempo=info.n; + if (upd) send_uev('dr0'); // app_local->tempoView->tempo->draw(); + break; + case eRed_att_timbre: + red_control->start_timbre->value.set(info.idata.d0,info.idata.d1); + if (upd) send_uev('dr1'); // red_control->start_timbre->draw(); + red_control->set_start_timbre(); + break; + case eRed_timbre: + red_control->timbre->value.set(info.idata.d0,info.idata.d1); + if (upd) send_uev('dr2'); // red_control->timbre->draw(); + red_control->set_timbre(); + break; + case eRed_attack: + red_control->startup->value()=info.n; + if (upd) send_uev('dr3'); // red_control->startup->draw(); + red_control->set_startup(); + break; + case eRed_stamp: + red_control->start_amp->value=info.n; + if (upd) send_uev('dr4'); // red_control->start_amp->draw(); + red_control->set_start_amp(); + break; + case eRed_decay: + red_control->decay->value()=info.n; + if (upd) send_uev('dr5'); // red_control->decay->draw(); + red_control->set_decay(); + break; + case ePurple_a_harm: { + static int st_harmonics[harm_max]; + for (n=0;nst_harm[n]->value=st_harmonics[n]=info.n5[n]; + if (upd) { + for (n=0;nst_harm[n]->draw(); + purple_control->set_st_hs_ampl(st_harmonics); + } + } + break; + case ePurple_s_harm: { + static int harmonics[harm_max]; + for (n=0;nharm[n]->value=harmonics[n]=info.n5[n]; + if (upd) { + for (n=0;nharm[n]->draw(); + purple_control->set_hs_ampl(harmonics); + } + } + break; + case ePurple_attack: + purple_control->start_dur->value()=info.n; + if (upd) send_uev('dr8'); // purple_control->start_dur->draw(); + purple_control->set_start_dur(); + break; + case eBlue_attack: + blue_control->attack->value()=info.n; + if (upd) send_uev('dr9'); // blue_control->attack->draw(); + blue_control->set_attack(); + break; + case eBlue_decay: + blue_control->decay->value()=info.n; + if (upd) send_uev('dr10'); // blue_control->decay->draw(); + blue_control->set_decay(); + break; + case eBlue_durlim: + blue_control->dur_limit->value()=info.n; + if (upd) send_uev('dr21'); // blue_control->dur_limit->draw(); + blue_control->set_durlim(); + break; + case eBlue_lowp: + blue_control->lowpass->value()=info.n; + if (upd) send_uev('dr27'); // blue_control->lowpass->draw(); + blue_control->set_lowpass(); + break; + case eRed_durlim: + red_control->dur_limit->value()=info.n; + if (upd) send_uev('dr22'); // red_control->dur_limit->draw(); + red_control->set_durlim(); + break; + case eBlue_rich: + blue_control->rich->value=info.b; + if (upd) send_uev('dr11'); // blue_control->rich->draw(); + break; + case eBlue_chorus: + blue_control->chorus->value=info.b; + if (upd) send_uev('dr12'); // blue_control->chorus->draw(); + break; + case eBlack_fm: + black_control->fm_ctrl->value.set(info.idata.d0,info.idata.d1); + if (upd) { + black_control->setfm(eModFreq); + black_control->setfm(eModIndex); + send_uev('dr13'); // black_control->fm_ctrl->draw(); + } + break; + case eBlack_attack: + black_control->attack->value()=info.n; + if (upd) send_uev('dr14'); // black_control->attack->draw(); + black_control->set_attack(); + break; + case eBlack_decay: + black_control->decay->value()=info.n; + if (upd) send_uev('dr15'); // black_control->decay->draw(); + black_control->set_decay(); + break; + case eBlack_detune: + black_control->detune->value()=info.n; + if (upd) send_uev('dr16'); // black_control->detune->draw(); + black_control->set_detune(); + break; + case eBrown_fm: + brown_control->fm_ctrl->value.set(info.idata.d0,info.idata.d1); + if (upd) { + brown_control->setfm(eModFreq); + brown_control->setfm(eModIndex); + send_uev('dr17'); // brown_control->fm_ctrl->draw(); + } + break; + case eBrown_detune: + brown_control->detune->value()=info.n; + if (upd) send_uev('dr18'); // brown_control->detune->draw(); + brown_control->set_detune(); + break; + case eBrown_attack: + brown_control->attack->value()=info.n; + if (upd) send_uev('dr19'); // brown_control->attack->draw(); + brown_control->set_attack(); + break; + case eBrown_decay: + brown_control->decay->value()=info.n; + if (upd) send_uev('dr20'); // brown_control->decay->draw(); + brown_control->set_decay(); + break; + case eGreen_timbre1: + green_control->timbre1->value.set(info.idata.d0,info.idata.d1); + green_control->set_timbre1(); + if (upd) send_uev('dr64'); // green_control->timbre1->draw(); + break; + case eGreen_timbre2: + green_control->timbre2->value.set(info.idata.d0,info.idata.d1); + green_control->set_timbre2(); + if (upd) send_uev('dr65'); // green_control->timbre2->draw(); + break; + case eGreen_attack: + green_control->attack->value()=info.n; + if (upd) send_uev('dr24'); // green_control->attack->draw(); + green_control->set_attack(); + break; + case eGreen_decay: + green_control->decay->value()=info.n; + if (upd) send_uev('dr25'); // green_control->decay->draw(); + green_control->set_decay(); + break; + case eGreen_fmul: + green_control->freq_mult->value()=info.n; + green_control->set_freq_mult(); + if (upd) send_uev('dr23'); // green_control->freq_mult->draw(); + break; + case eGreen_chorus: + green_control->chorus->value()=info.n; + green_control->set_freq_mult(); + if (upd) send_uev('dr26'); // green_control->chorus->draw(); + break; + case eSynth: { + int col=info.ms_data.col; + CtrlBase *ctr=col2ctrl(col); + ctr->instrm_val=eSynthMode; + if (upd) { + ctr->synth->update_values(&info.ms_data,false); + send_uev('dr33',col); + } + } + break; + case eMode: + col2ctrl(info.idata.col)->set_instr_mode(info.idata.d1,upd); + break; + case eRevb: + if (upd) { + app_local->revb_view->reverb->set_hvsval(Int2(info.idata.d1,info.idata.d2),1,false); // fire, not draw + app_local->revb_view->sel_col=info.idata.col; + col2ctrl(info.idata.col)->set_revb(info.idata.d1,info.idata.d2); + send_uev('dr40'); // revb_view->reverb->draw(), revb_view->draw() + } + break; + case ePurple_tone: + purple_control->sound->set_rbutnr(info.n,true); + if (upd) send_uev('dr41'); // purple_control->sound->draw() + break; + case ePhysM: + col2phm_ctrl(info.idata.col)->set_mode(ePhysmodMode,info); + set_phm(info,upd,info.idata.col,'dr58'); + break; + case eRed_tone: + red_control->tone->set_rbutnr(info.n,true); + if (upd) send_uev('dr55'); // red_control->tone->draw() + break; + case ePurple_decay: + purple_control->decay->value()=info.n; + if (upd) send_uev('dr43'); // purple_control->decay->draw(); + purple_control->set_decay(); + break; + case eBlack_mmod: + black_control->mod_mod->value.set(info.idata.d0,info.idata.d1); + if (upd) { + black_control->set_mmod(); + send_uev('dr44'); // black_control->mod_mod->draw(); + } + break; + case eBrown_mmod: + brown_control->mod_mod->value.set(info.idata.d0,info.idata.d1); + if (upd) { + brown_control->set_mmod(); + send_uev('dr45'); // brown_control->mod_mod->draw(); + } + break; + case eWaveF: + col2phm_ctrl(info.idata.col)->set_mode(eWaveMode,info); + if (!read_wavef(upd,info,'dr46')) return false; + break; + case eWaveAmpl: + col2phm_ctrl(info.idata.col)->wave_ampl->value()=info.idata.d1; + col2phm_ctrl(info.idata.col)->set_ampl_txt(info.idata.d1); + if (upd) send_uev('dr49',info.idata.col); + break; + case eCtrPitch: + col2phm_ctrl(info.idata.col)->ctrl_pitch->value=info.idata.b; + if (upd) send_uev('dr50',info.idata.col); + break; + case eBaseFreq: + col2FMctrl(info.idata.col)->base_freq->value=info.idata.b; + if (upd) { + col2FMctrl(info.idata.col)->setfm(eModFreq); + send_uev('dr51',info.idata.col); + } + break; + case eAmpl: { + CtrlBase *ctr=col2ctrl(info.idata.col); + for (int i=0;i<3;++i) ctr->ampl_val[i]=info.idata.d1; + ctr->set_ampl_txt(info.idata.d1); + } + if (upd) send_uev('dr52',info.idata.col); + break; + case eAmpl_gr: { + int val=info.idata.d1, + gr=info.idata.d2; + CtrlBase *ctr=col2ctrl(info.idata.col); + ctr->ampl_val[gr].value=val; + if (ctr->group->act_rbutnr()==gr) { + ctr->set_ampl_txt(val); + if (upd) send_uev('dr52',info.idata.col); + } + } + break; + case eLoc: col2ctrl(info.idata.col)->set_loc(info.idata.d1); break; + case eSLoc: col2phm_ctrl(info.idata.col)->set_sampled_loc(info.idata.d1); break; + case eIsa: col2ctrl(info.idata.col)->set_isa(info.idata.d1); break; + case eMsIsa: col2ctrl(info.idata.col)->set_ms_isa(info.idata.d1); break; + case eIsa_gr: col2ctrl(info.idata.col)->set_isa(info.idata.d1,info.idata.d2); break; + case eMidiInstr: // then: upd = false + for (n=0;nsynth->map_topwin(true); + ctrl->map_win(true); + } + else { + ctrl->synth->map_topwin(false); + } +} + +void checkbox_cmd(Id cmd,bool val) { + switch (cmd.id1) { + case 'nois': // from phm control, add noise + phm_buf.set_phys_model(cmd.id2,0); + break; + default: + alert("checkbox_cmd: unk cmd %d",cmd.id1); + } +} + +void *call_browser(void*) { + char buf[100]; + snprintf(buf,100,"%s /usr/share/doc/amuc/amuc-man.html",help_browser); + if (system(buf)) alert("Browser for help-page not available (hint: modify .amucrc)"); + return 0; +} + +void menu_cmd(Id id) { + switch (id.id1) { + case 'mkwa': + if (!i_am_playing) { + app->dia_wd->dlabel("wave file:",cRose); + app->dia_wd->ddefault("out.wav",dial_cmds.create_wav); + } + break; + case 'mkmi': + if (!i_am_playing) { + app->dia_wd->dlabel("midi file:",cRose); + app->dia_wd->ddefault("out.mid",dial_cmds.create_midi); + } + break; + case 'mkps': + if (!i_am_playing) { + app->dia_wd->dlabel("postscript file:",cRose); + app->dia_wd->ddefault("out.ps",dial_cmds.create_ps); + } + break; + case 'imid': + if (!i_am_playing) { + if (!app->midi_in_file.s[0]) app->midi_in_file.cpy("in.mid"); + app->dia_wd->dlabel("midi file:",cRose); + app->dia_wd->ddefault(app->midi_in_file.s,dial_cmds.import_midi); + } + break; + case 'man': { + pthread_t sys_thread; + pthread_create(&sys_thread,0,call_browser,0); + } + break; + case 'abou': + alert(" Amuc - the Amsterdam Music Composer"); + alert(""); + alert("version#66: %s",version); + alert("author#66: W.Boeke"); + alert("email#66: w.boeke@chello.nl"); + alert("homepage#66: members.chello.nl/w.boeke/amuc/index.html"); + break; + default: alert("menu_cmd: unknown cmd %d",id.id1); + } +} + +void button_cmd(Id id) { + switch (id.id1) { + case 'uns': // unselect + app->act_action='u'; // no break; + case 'rcol': // re-color selected + case 'del ': // delete selection + case 'shmu': case 'shmd': // shift move up, down + case 'shcu': case 'shcd': // shift copy up, down + if (selected.sv) selected.sv->modify_sel(id.id1); + app_local->mouseAction->reset(); + app_local->info_view->set_modif(eSco,true); + break; + case 'ok': + app->dia_wd->dok(); + break; + case 'eq': { + CtrlBase *ctr=col2ctrl(id.id2); + ctr->set_isa((ctr->isa[ctr->group ? ctr->group->act_rbutnr() : 0]->ctrl_col+1)%colors_max); + } + break; + case 'revc': { + ReverbView *rv=app_local->revb_view; + rv->sel_col=(rv->sel_col+1)%colors_max; + col2ctrl(rv->sel_col)->set_revb(rv->reverb->value.x,rv->reverb->value.y); + ReverbView::draw(0); + } + break; + case 'cwf': { // re-collect wave files + PhmCtrl *pc=col2phm_ctrl(id.id2); + pc->file_select->reset(); + if (wave_buf.sample_dirs->coll_wavefiles()) { + for (int i=0;i<=wave_buf.fname_nr;++i) { + FileName *fn=wave_buf.filenames+i; + pc->file_select->add_mbut(fn->name,fn->col); + } + pc->file_select->init_mwin(); + } + } + break; + default: alert("button_cmd: unk cmd %d",id.id1); + } +} + +void rbutton_cmd(Id id,int nr,int fire) { + const int m1[2]={ eNativeMode,eSynthMode }; + const int m2[2]={ ePhysmodMode,eWaveMode }; + PhmCtrl *pct=col2phm_ctrl(id.id2); + switch (id.id1) { + case 'smod': // from phys mod ctrl, sample mode + pct->sample_mode=m2[nr]; + if (pct->sample_mode==ePhysmodMode) { + pct->mode->re_parent(pct->subwin1); + pct->wave_ampl->re_parent(pct->subwin1); + pct->ctrl_pitch->re_parent(pct->subwin1); + pct->file_select->reset(); + hide_window(pct->subwin2); + map_window(pct->subwin1); + } + else { + pct->mode->re_parent(pct->subwin2); + pct->wave_ampl->re_parent(pct->subwin2); + pct->ctrl_pitch->re_parent(pct->subwin2); + hide_window(pct->subwin1); + map_window(pct->subwin2); + } + break; + case 'moms': // mode = mono synthesizer + { CtrlBase *ct=col2ctrl(id.id2); + ct->instrm_val=m1[nr]; + set_msynth(ct,nr==1,id.id2); + } + break; + default: alert("rbutton_cmd: unk cmd %d",id.id1); + } +} + +void exp_rbutton_cmd(Id cmd,bool is_act) { + if (!is_act) { + app->act_action=0; + return; + } + switch (cmd.id1) { + case 'up': app->act_action=XK_Up; break; + case 'do': app->act_action=XK_Down; break; + case 'left': app->act_action=XK_Left; break; + case 'ninf': app->act_action='i'; break; + case 'sel': app->act_action='s'; break; + case 'scol': app->act_action='scol'; break; + case 'move': app->act_action='m'; break; + case 'copy': app->act_action='c'; break; + case 'prta': app->act_action='p'; break; + case 'all': app->act_action='a'; break; + case 'allc': app->act_action='allc'; break; + case 'muln': app->act_action='n'; break; + default: alert("exp_rbutton_cmd: unk cmd %d",cmd.id1); + } +} + +void hvslider_cmd(Id id,Int2 val,char *&text_x,char *&text_y,bool rel) { + switch (id.id1) { + case 'rval': { // reverb value + ReverbView *rv=app_local->revb_view; + col2ctrl(rv->sel_col)->set_revb(val.x,val.y); + set_text(rv->reverb->text_x, + val.x==0 ? 0 : + val.x==1 ? "0.5 short" : + val.x==2 ? "0.5 long" : + val.x==3 ? "1.0 short" : + val.x==4 ? "1.0 long" : "?"); + } + break; + case 'fm ': { // from black_ or brown_control, fm freq, fm index + FMCtrl *fmc=col2FMctrl(id.id2); + fmc->setfm(eModFreq); + fmc->setfm(eModIndex); + } + break; + case 'momo': // from black_ or brown_control, mod mod + if (rel) col2FMctrl(id.id2)->set_mmod(); + break; + case 'radn': // from red_control, attack diff/nrsin + app->red_control->set_start_timbre(); + break; + case 'rsdn': // from red_control, sustain diff/nsin + app->red_control->set_timbre(); + break; + case 'grw1': // from green_control, diff/nsin wave1 + app->green_control->set_timbre1(); + break; + case 'grw2': // from green_control, diff/nsin wave2 + app->green_control->set_timbre2(); + break; + case 'spte': // from phys mod ctrl, speed_tension + if (rel) phm_buf.set_phys_model(id.id2,0); + break; + default: alert("hvslider_cmd: unk cmd %d",id.id1); + } +} + +void slider_cmd(Id id,int val,int fire,char *&text,bool rel) { + PhmCtrl *pct; + FMCtrl *fmctr; + switch (id.id1) { + case 'deca': // from phys mod ctrl, decay + if (rel) phm_buf.set_phys_model(id.id2,0); + break; + case 'grfm': // freq ratio green instrument + app->green_control->set_freq_mult(); + break; + case 'iamp': { + CtrlBase *ctr=col2ctrl(id.id2); + ctr->ampl_val[ctr->group ? ctr->group->act_rbutnr() : 0].value=val; + set_text(ctr->ampl->text,"%.2f",ampl_mult[val]); // extern SliderData + } + break; + case 'fmat': + fmctr=col2FMctrl(id.id2); + fmctr->set_attack(); + break; + case 'fmde': + fmctr=col2FMctrl(id.id2); + fmctr->set_decay(); + break; + case 'rest': // startup red instrument + app->red_control->set_startup(); + break; + case 'samp': // start-ampl red instrument + app->red_control->set_start_amp(); + break; + case 'rede': // decay red instrument + app->red_control->set_decay(); + break; + case 'grat': // attack green instrument + app->green_control->set_attack(); + break; + case 'grde': // decay green instrument + app->green_control->set_decay(); + break; + case 'purd': // decay purple instrument + app->purple_control->set_decay(); + break; + case 'blat': // attack blue instrument + app->blue_control->set_attack(); + break; + case 'blde': // decay blue instrument + app->blue_control->set_decay(); + break; + case 'durl': // duration limit instrument + switch (id.id2) { + case eBlue: app->blue_control->set_durlim(); break; + case eRed: app->red_control->set_durlim(); break; + } + break; + case 'lpas': // lowpass blue instr + app->blue_control->set_lowpass(); + break; + case 'fmdt': // detune black or brown instrument + fmctr=col2FMctrl(id.id2); + fmctr->set_detune(); + break; + case 'puah': { // update attack harmonics purple instr + static int harmonics[harm_max]; + for(int n=0;npurple_control->st_harm[n]->value; + app->purple_control->set_st_hs_ampl(harmonics); + } + break; + case 'purp': { // update purple instr + static int harmonics[harm_max]; + for(int n=0;npurple_control->harm[n]->value; + app->purple_control->set_hs_ampl(harmonics); + } + break; + case 'pusu': // startup duration purple instr + app->purple_control->set_start_dur(); + break; + case 'wamp': // wave ampl sampled instr + pct=col2phm_ctrl(id.id2); + pct->set_ampl_txt(val); + break; + case 'phwa': // wave ampl sampled instr + pct=col2phm_ctrl(id.id2); + pct->set_ampl_txt(val); + break; + case 'semt': // shift semitones + set_text(text,"%d semi t.",app_local->mouseAction->semi_tones->value); + break; + default: alert("slider_cmd: unk cmd %s",cconst2s(id.id1)); + } +} + +void rbutwin_cmd(Id id,int butnr,int fire) { + switch (id.id1) { + case 'fmgr': { // amplitude group + CtrlBase *ctr=col2ctrl(id.id2); + ctr->ampl->d=ctr->ampl_val+butnr; + ctr->ampl->draw(); + ctr->draw_isa_col(); + } + case 'purt': // sound-mode purple instrument + app->purple_control->set_tone(); + break; + case 'redt': // tone-mode red instrument + app->red_control->set_tone(); + break; + case 'grnr': { // score group nr + ScoreView *sv=the_sv(id.id2); + if (sv->score) { + ScSection *sec; + sv->score->ngroup=butnr; + for (int lnr=0;lnrscore->len;++snr) { + sec=sv->score->get_section(lnr,snr); + if (sec->cat==ePlay) + for (;sec;sec=sec->nxt()) sec->s_group=butnr; + } + } + app_local->info_view->set_modif(eSco,true); + } + break; + default: alert("rbutwin_cmd: unk cmd %d",id.id1); + } +} + +void menu_cmd(Id id,ChMButton *mb) { + PhmCtrl *pct; + switch (id.id1) { + case 'fsel': // read sample wave file + pct=col2phm_ctrl(id.id2); + if (pct->just_listen->value) { + if (!i_am_playing && !wf_playing) { + app->stop_requested=false; + app->scopeView->reset(); + wl_buf.reset(); + FileName *fn=wave_buf.filenames+mb->nr; + if (read_wav(fn->dir,fn->name,&wl_buf)) { + if (output_port==eAlsa) { + pthread_create(&thread3, 0, wave_listen, 0); + } + else wf_playing=true; + } + } + } + else { + pct->reset_wf_sliders(true); + ShortBuffer *wd=wave_buf.w_buf + pct->ctrl_col; + wd->reset(); + pct->file_select->reset(); + FileName *fn=wave_buf.filenames+mb->nr; + if (read_wav(fn->dir,fn->name,wd)) { + pct->file_select->cp_value(mb->nr,mb->label); + pct->file_select->draw(); + } + } + break; + default: alert("menu_cmd: unk cmd %d",id.id1); + } +} + +ScSection::ScSection(): + s_col(eBlack),cat(eSilent),sign(0), + stacc(false),sampled(false),sel(false),sel_color(false), + port_dsnr(0), + del_start(0),del_end(0),s_group(0), + port_dlnr(0), + nxt_note(0), + src_tune(0) { +} + +void ScSection::reset() { + static const ScSection sec; + *this=sec; +} + +ScSection* ScSection::nxt() { + if (nxt_note) return mn_buf.buf+nxt_note; + return 0; +} + +bool ScSection::prepend(ScSection *from,int sctype,uint *lst_cp) { // *lst_cp = index of last copied ScSection + int ind,ind2; + if (cat==ePlay) { + if (!(ind2=mn_buf.new_section(sctype))) return false; + mn_buf.buf[ind2]=*this; + *this=*from; + if (nxt_note) { + if (!(ind=nxt()->copy_to_new_sect(sctype,lst_cp))) return false; + nxt_note=ind; + } + else + if (lst_cp) *lst_cp=0; + for (ScSection *sec=this;;sec=sec->nxt()) + if (!sec->nxt_note) { sec->nxt_note=ind2; break; } + } + else { // cat==eSilent + *this=*from; + if (nxt_note) { + if (!(ind=nxt()->copy_to_new_sect(sctype,lst_cp))) return false; + nxt_note=ind; + } + } + return true; +} + +Score::Score(const char *nam,int length,uint sctype): + name(nam ? tnames.add(nam) : -1), + len(length), + lst_sect(-1), + end_sect(0), + sc_key_nr(0), + signs_mode(0), + sc_meter(0), + ngroup(0), + sc_type(sctype), + scInfo(sctype==eMusic ? new ScInfo[len] : 0), + block(new ScSection[sclin_max*len]) { + for (int i=0;i=required) return false; + ScInfo *old_info; + ScSection *old_block=block; + int n,n2, + old_len=len; + if (debug) printf("len incr: len=%d req=%d\n",len,required); + while (lenreset(); + } + } + else { + len=max; + ScSection *old_block=block; + block=new ScSection[sclin_max*len]; + for (lnr=0;lnrnxt()) { + if (from && + sec->s_col==from->s_col && + sec->s_group==from->s_group && + sec->sampled==from->sampled) // equal colors cannot be played together + break; + if (!sec->nxt_note) { + sec->nxt_note=mn_buf.new_section(typ); + if (sec->nxt_note) + sec=sec->nxt(); + break; + } + } + return sec; +} + +void Score::copy_keyb_tune() { + int n,n2; + KeybNote *note; + ScSection *to; + note=kb_tune.buf + kb_tune.cur_ind; + end_sect=note->snr+note->dur+1; + check_len(end_sect); + for (n=0;n<=kb_tune.cur_ind;++n) { + note=kb_tune.buf+n; + to=lin[note->lnr].sect + note->snr; + for (n2=0;n2dur;++n2,++to) { + to->cat=ePlay; + to->sign=note->sign; + to->s_col=note->col; + } + } +} + +void Score::insert_midi_note(int time,int time_end,int lnr,int col,int sign) { + int n, + snr_start=time/subdiv, + snr_end=time_end/subdiv, + del_start=time%subdiv, + del_end=time_end%subdiv; + if (snr_start==snr_end) { // zero-length note? + if (debug) printf("ins_midi_note zero length: %d %d %d %d\n",time,time_end,lnr,col); + ++snr_end; + del_end=0; + } + ScSection *to,*to1, + skip; // for preventing equal multiple notes + + if (debug) printf("ins_midi_note: %d %d %d %d %d\n",time,time_end,lnr,col,sign); + skip.sampled=false; + skip.s_group=0; + skip.s_col=col; + if (end_sect <= snr_end) { + end_sect=snr_end+1; + check_len(end_sect); + } + to=to1=lin[lnr].sect + snr_start; + if (snr_start>0 && (to-1)->cat==ePlay && to->cat!=ePlay) (to-1)->stacc=true; // keep notes separate, unless multiple + for (n=snr_start;;++n,++to) { + to1=to; + if (to->cat==ePlay) { // multi note? + to1=to->get_the_section(&skip,0); + } + to1->cat=ePlay; + to1->sampled=false; + to1->s_col=col; + to1->sign=sign; + to1->s_group=ngroup; + if (n==snr_start) to1->del_start=del_start; + if (n==snr_end-1) { to1->del_end=del_end; break; } + } +} + +void Score::insert_midi_perc(int time,int lnr,int col) { + int snr_start=time/subdiv, + del_start=time%subdiv; + ScSection *to=0, + skip; // for preventing equal multiple notes + + if (debug) printf("ins_midi_perc: %d %d %d\n",time,lnr,col); + skip.sampled=true; + skip.s_group=0; + skip.s_col=col; + if (end_sect <= snr_start) { + end_sect=snr_start+1; + check_len(end_sect); + } + for (int lnr1=lnr;lnr>=0;--lnr1) { // no multi notes! + to=lin[lnr1].sect + snr_start; + if (to->cat==eSilent) break; + } + to->cat=ePlay; + to->sampled=true; + to->s_col=col; + to->del_start=del_start; +} + +void mouse_down(Id id,int x,int y,int button) { + switch (id.id1) { + case 'scmn': + the_sv(id.id2)->mouseDown_mnr(x,y,button); + break; + case 'scv': + the_sv(id.id2)->mouseDown(x,y,button); + break; + case 'mmnr': + the_mv()->mouseDown_mnr(x,y,button); + break; + case 'muv': + the_mv()->mouseDown(x,y,button); + break; + default: alert("mouse_down: unk sender"); + } +} + +void mouse_moved(Id id,int x,int y,int button) { + switch (id.id1) { + case 'scv': + the_sv(id.id2)->mouseMoved(x,y,button); + break; + default: alert("mouse_moved: unk sender"); + } +} + +void mouse_up(Id id,int x,int y,int button) { + switch (id.id1) { + case 'scv': + the_sv(id.id2)->mouseUp(x,y,button); + break; + case 'muv': + the_mv()->mouseUp(x,y,button); + break; + default: alert("mouse_up: unk sender"); + } +} + +MusicView::MusicView(Rect rect): + ScoreViewBase(rect,eMusic) { + score=new Score(0,sect_mus_max,eMusic); + mnrview=new BgrWin(top_win->win,Rect(rect.x,rect.y,rect.width,mnr_height), + FN,draw_cmd,mouse_down,0,0,cBgrGrey,Id('mmnr')); + set_custom_cursor(mnrview->win,text_cursor); + scview=new BgrWin(top_win->win,Rect(rect.x,rect.y+mnr_height,rect.width,rect.height-scrollbar_wid-tnview_height-mnr_height), + FN,draw_cmd,mouse_down,0,mouse_up,cWhite,Id('muv')); + set_custom_cursor(scview->win,up_arrow_cursor); + scroll=new HScrollbar( + top_win->win,Rect(rect.x,rect.y+rect.height-scrollbar_wid-tnview_height+1,rect.width,0), + Style(1,0,sect_length),FN,rect.width,scroll_cmd); + tnview=new BgrWin(top_win->win,Rect(rect.x,rect.y+rect.height-tnview_height+2,rect.width,tnview_height), + FN,draw_cmd,cBgrGrey,1,Id('minf')); +} + +void Scores::swap(int ind1,int ind2) { + int label=buf[ind1]->name; + RButton *rb=buf[ind1]->rbut; + static RButWin *rbw=app_local->tunesView->rbutwin; + rbw->re_label(rb,tnames.txt(buf[ind2]->name)); + buf[ind1]->rbut=buf[ind2]->rbut; + rbw->re_label(buf[ind2]->rbut,tnames.txt(label)); + buf[ind2]->rbut=rb; + Score *sc=buf[ind1]; + buf[ind1]=buf[ind2]; buf[ind2]=sc; +} + +void Scores::remove(Score *sc) { + int sc_ind, + ind; + if ((sc_ind=get_index(sc))<0) return; + static RButWin *rbw=app_local->tunesView->rbutwin; + for (ind=sc_ind;indact_tune==sc) app->act_tune=0; + rbw->del_rbut(sc->rbut); // now: sc->rbut = act_button = 0 + delete sc; + --lst_score; +} + +void MusicView::exec_info_t0() { + if (exec_info(0,score,false)) exec_info(0,score,true); +} + +void MusicView::draw_tune_names() { + tnview->clear(); + for (int n=leftside;nlen;n++) + draw_tune_name(n); +} + +void MusicView::draw_cmd(Id id) { + switch (id.id1) { + case 'muv': the_mv()->draw_sc2(false); break; + case 'minf': the_mv()->draw_tune_names(); break; + } +} + +void ColorView::color_cmd(Id id,int nr,int fire) { + app->act_color=nr; + app->act_instr_ctrl->map_win(false); + app->act_instr_ctrl=app->sampl->value ? col2phm_ctrl(app->act_color) : col2ctrl(app->act_color); + app->act_instr_ctrl->map_win(true); +} + +void CtrlBase::map_win(bool map) { + if (map) // synth not mapped + cview->map(); + else { + if (synth) // 0 if PhmCtrl + hide_window(synth->topwin); + if (sample_mode==eWaveMode) + static_cast(this)->file_select->reset(); + cview->hide(); + } +} + +void CtrlBase::set_isa(int col) { + for (int gr=0;gr<3;++gr) isa[gr]=col2ctrl(col); + send_uev('sisa',ctrl_col); +} + +void CtrlBase::set_ms_isa(int col) { + synth->set_isa(col2ctrl(col)->synth); + send_uev('msis',ctrl_col); +} + +void CtrlBase::set_isa(int col,int gr) { + isa[gr]=col2ctrl(col); + if (group->act_rbutnr()==gr) + send_uev('sisa',ctrl_col); +} + +void MeterView::meter_cmd(Id,int val,int,char *&txt,bool rel) { + MeterView *mev=app_local->meterView; + if (app->act_score>nop) { + ScoreView *sv=the_sv(app->act_score); + if (!sv->score) return; + int cur_m=sv->score->sc_meter; + if (val<0) { // out of range? + set_text(txt,"%d",cur_m); + } + else { + if (mev->m[val]!=cur_m) { + set_text(txt,"%d",mev->m[val]); + if (rel) { + sv->score->sc_meter=mev->m[val]; + sv->draw_sc2(true); + } + } + } + } + else { + int cur_m=app->act_meter; + if (val<0) { // might be set by draw() + set_text(txt,"%d",cur_m); + } + else { + set_text(txt,"%d",mev->m[val]); + if (rel) { + app->act_meter=mev->m[val]; + the_sv(0)->draw_sc2(true); + the_sv(1)->draw_sc2(true); + the_mv()->draw_sc2(true); + } + } + } + if (rel) app->check_reset_as(); +} + +void App::check_reset_as() { // do not reset act_score if shift key pressed + if (key_pressed!=XK_Shift_L) { + active_scoreCtrl->reset(); + act_score=nop; + } +} + +void TunesView::tune_cmd(Id,int nr,int) { + ScoreView *sv; + app->act_tune_ind=nr; + app->act_tune=scores[nr]; + if (app->act_score>nop) { + if (!app->act_tune) return; + sv=the_sv(app->act_score); + if (selected.sv==sv) selected.restore_sel(); + sv->assign_score(app->act_tune,true); + app->check_reset_as(); + } +} + +void TunesView::scr_cmd(Id,int val,int,bool) { + static RButWin *rb=app_local->tunesView->rbutwin; + rb->set_y_off(val); +} + +void AppLocal::modt_cmd(Id) { + MusicView *mv=app_local->musicView; + EditScript *es=app_local->editScript; + char text_buf[2000]; + app->modify_script(es,mv->play_start,mv->play_stop); + es->textview->get_text(text_buf,2000); + mv->reset(true,false,false); + if (app->run_script(text_buf)) { + mv->set_scroll_range(); mv->draw_sc2(false); mv->exec_info_t0(); + } + app_local->info_view->set_modif(eScr,true); +} + +void AppLocal::clear_cmd(Id) { + if (app->act_score>nop) { + ScoreView *sv=the_sv(app->act_score); + if (sv->score) { + if (selected.sv==sv) + selected.reset(); + sv->leftside=0; sv->scroll->value=0; + sv->score->reset(true); + sv->set_scroll_range(); + sv->draw_sc2(true); + app_local->info_view->set_modif(eSco,true); + } + app->check_reset_as(); + } + else { + MusicView *mv=the_mv(); + if (mv->score->lst_sect>=0) { + mv->reset(true,true,true); + mv->draw_sc2(false); + } + } +} + +void AppLocal::remove_cmd(Id) { + if (!app->act_tune) return; + ScoreView *sv; + if (app->find_score(app->act_tune,sv)) { + if (selected.sv==sv) selected.reset(); + sv->reset(); + } + scores.remove(app->act_tune); + app_local->info_view->set_modif(eSco,true); +} + +void Selected::restore_sel() { + SLList_elem *sd; + ScSection *sec; + for (sd=sd_list.lis;sd;sd=sd->nxt) { + sec=sv->score->get_section(sd->d.lnr,sd->d.snr); + sec->sel=false; + sec->drawSect(sv,sd->d.snr,sd->d.lnr); + if (sv->score==sv->other->score) + sec->drawSect(sv->other,sd->d.snr,sd->d.lnr); + } + reset(); +} + +void ScoreView::select_all(int start) { + const int end= score->end_sect ? score->end_sect : score->len; + for (int snr=start;snrend_sect ? score->end_sect : score->len; + for (int snr=start;snract_color,app->sampl->value); +} + +void ScoreView::sel_column(int snr) { + ScSection *sect; + for (int lnr=0;lnrget_section(lnr,snr); + if (sect->cat==ePlay && !(draw_mode==ePiano && sect->sampled)) { // mode ePiano: sampled sections not visible + selected.insert(lnr,snr,sect->sign); + sect->sel=true; sect->sel_color=false; + sect->drawSect(this,snr,lnr); + } + } +} + +void ScoreView::sel_column_1col(int snr,uint col,bool sampled) { + ScSection *sect,*sec; + for (int lnr=0;lnrget_section(lnr,snr); + if (sect->cat==ePlay) { + for (sec=sect;sec;sec=sec->nxt()) { + if (sec->s_col==col && sec->sampled==sampled && !(draw_mode==ePiano && sec->sampled)) { + if (sec!=sect) { // place *sec at frontside + ScSection s1(*sect),s2(*sec); + *sect=s2; sect->nxt_note=s1.nxt_note; + *sec=s1; sec->nxt_note=s2.nxt_note; + } + selected.insert(lnr,snr,sect->sign); + sect->sel=sect->sel_color=true; + sect->drawSect(this,snr,lnr); + break; + } + } + } + } +} + +void ScoreViewBase::draw_meter_nrs(Score *sc) { + int meter= score->sc_meter==0 ? app->act_meter : score->sc_meter; + Str str; + for (int snr=leftside;snrlen;++snr) { + if (snr%meter == 0) { + int x=(snr-leftside)*sect_len; + if (xdx) + xft_draw_string(mnrview->xft_win,xft_Black,Point(x,mnr_height-2),str.tos(snr/meter)); + else break; + } + } +} + +void ScoreViewBase::draw_start_stop(Score *sc) { + int x, + y=5; + mnrview->clear(); + draw_meter_nrs(sc); + set_color(cRed); + if (play_start>0) { + x=(play_start-leftside)*sect_len; + Point pts[3]={ Point(x,y+5),Point(x+6,y),Point(x,y-5) }; + mnrview->fill_polygon(pts,3); + } + if (play_stop>0) { + x=(play_stop-leftside+1)*sect_len; + Point pts[3]={ Point(x,y+5),Point(x,y-5),Point(x-6,y) }; + mnrview->fill_polygon(pts,3); + } +} + +void ScoreViewBase::enter_start_stop(Score *sc,int snr,int mouse_but) { + switch (mouse_but) { + case Button1: + if (play_start==snr) + play_start=0; + else { + play_start=snr; + if (play_stop>=0 && play_stop=sclin_max || snr<0) return; + key=key_pressed; + if (key) { + if (key==XK_Shift_L) { + keep_m_action=true; + key=app->act_action; + } + else app->act_action=key; + } + else + key=app->act_action; + ScSection *sec=score->get_section(lnr,snr); + uint cat=sec->cat; + static MouseAction *ma=app_local->mouseAction; + switch (key) { + case 'i': // note info + ma->ctrl->set_rbut(ma->but_ninf,false); + if (draw_mode==ePiano) { + alert("note info: not available in 'white keys' mode"); + break; + } + if (cat==ePlay) { + draw_info(sec,true); + } + break; + case XK_Shift_L: + case 0: + break; + default: + alert("unexpected key pressed"); + } +} + +void MusicView::mouseUp(int x,int y,int mouse_but) { + static MouseAction *ma=app_local->mouseAction; + if (keep_m_action) keep_m_action=false; + else ma->reset(); +} + +void ScoreView::mouseDown(int x,int y,int mouse_but) { + if (!score) return; + if (key_pressed && textcursor_active()) alert("Warning: key pressed while cursor enabled!"); + state=eIdle; + int snr=sectnr(x); + uchar sign; + const int lnr=linenr(y,snr,sign); + if (lnr<0 || lnr>=sclin_max || snr<0) return; + if (mouse_but==Button3 && !(zoomed && draw_mode==eTimingValue)) { // right mouse button + if (score->end_sect) { + draw_endline(false); + if (other->score==score) other->draw_endline(false); + } + score->end_sect=snr; + if (score->check_len(snr+1)) { + set_scroll_range(); draw_sc2(false); + if (other->score==score) { other->set_scroll_range(); other->draw_sc2(false); } + } + else { + draw_endline(true); + if (other->score==score) other->draw_endline(true); + } + return; + } + if (snr>=score->len) return; + + app_local->info_view->set_modif(eSco,true); + ScSection *sec=score->get_section(lnr,snr), + proto; + uint cat=sec->cat, + col=sec->s_col; + key=key_pressed; + static MouseAction *ma=app_local->mouseAction; + bool to_higher=false, + to_lower=false, + multiple_note=false; + prev_snr=snr; + cur_lnr=lnr; + cur_snr=snr; + if (key) { + if (key==XK_Shift_L) { + keep_m_action=true; + key=app->act_action; + } + else app->act_action=key; + } + else + key=app->act_action; + switch (key) { + case XK_Up: + case XK_Down: + to_higher=make_higher[lnr%7]; + to_lower=make_lower[lnr%7]; + // no break + case XK_Left: + state=eToBeReset; + if (draw_mode==ePiano) + return; + for(sec=score->get_section(lnr,cur_snr);sec->cat==ePlay && sec->s_col==col;++snr,++sec) { + ScSection *sec1; + bool stop = snr>=score->len-1 || sec->stacc || sec->sampled; + if (score==other->score && other->draw_mode==ePiano) { // erase section in other view? + sec->cat=eSilent; sec->drawSect(other,snr,lnr); sec->cat=ePlay; + } + switch (key) { + case XK_Up: + ma->ctrl->set_rbut(ma->but_sharp,false); + if (to_higher) { + for (sec1=sec;sec1;sec1=sec1->nxt()) sec1->sign=0; + *score->get_section(lnr-1,snr)=*sec; + if (sec->sel) { + selected.remove(lnr,snr); + selected.insert(lnr-1,snr,0); + } + if (lnr>0) // maybe out-of-range + score->get_section(lnr-1,snr)->drawSect(this,other,snr,lnr-1); + sec->reset(); + } + else + for (sec1=sec;sec1;sec1=sec1->nxt()) sec1->sign=eHi; + break; + case XK_Down: + ma->ctrl->set_rbut(ma->but_flat,false); + if (to_lower) { + for (sec1=sec;sec1;sec1=sec1->nxt()) sec1->sign=0; + *score->get_section(lnr+1,snr)=*sec; + if (sec->sel) { + selected.remove(lnr,snr); + selected.insert(lnr+1,snr,0); + } + if (lnrget_section(lnr+1,snr)->drawSect(this,other,snr,lnr+1); + sec->reset(); + } + else + for (sec1=sec;sec1;sec1=sec1->nxt()) sec1->sign=eLo; + break; + case XK_Left: + ma->ctrl->set_rbut(ma->but_normal,false); + for (sec1=sec;sec1;sec1=sec1->nxt()) sec1->sign=0; + break; + } + sec->drawSect(this,other,snr,lnr); + if (stop) break; + } + return; + case 'n': // multiple note insert + multiple_note=true; + ma->ctrl->set_rbut(ma->but_multi,false); + break; + case 'a': // select all + ma->ctrl->set_rbut(ma->but_all,false); + if (selected.sv!=this) { + selected.restore_sel(); + selected.sv=this; + } + state=eToBeReset; + select_all(snr); + return; + case 'allc': // select all of 1 color + ma->ctrl->set_rbut(ma->but_all_col,false); + if (selected.sv!=this) { + selected.restore_sel(); + selected.sv=this; + } + state=eToBeReset; + select_all_1col(snr); + return; + case 'u': // unselect + if (selected.sv==this) { + selected.restore_sel(); + } + state=eToBeReset; + return; + case 's': // select + ma->ctrl->set_rbut(ma->but_select,false); + if (selected.sv!=this) { + selected.restore_sel(); + selected.sv=this; + } + if (cat==ePlay) { + ScSection *cur_sec=score->get_section(lnr,cur_snr); + bool b=cur_sec->sel; + for(sec=cur_sec;sec->cat==ePlay && sec->s_col==cur_sec->s_col && sec->sign==cur_sec->sign;++snr,++sec) { + if (b) { + if (sec->sel) selected.remove(lnr,snr); + } + else { + selected.insert(lnr,snr,sec->sign); + } + sec->sel=!b; sec->sel_color=false; + sec->drawSect(this,snr,lnr); + if (snr>=score->len-1 || sec->stacc || sec->sampled) break; + } + } + else { + state=eCollectSel; + sel_column(snr); + } + return; + case 'i': // note info + ma->ctrl->set_rbut(ma->but_ninf,false); + if (cat==ePlay) { draw_info(score->get_section(lnr,cur_snr),false); state=eToBeReset; } + return; + case 'scol': // select all of 1 color + if (selected.sv!=this) { + selected.restore_sel(); + selected.sv=this; + } + state=eCollectColSel; + sel_column_1col(snr,app->act_color,app->sampl->value); + return; + case 'm': // move + case 'c': // copy + if (!selected.sd_list.lis) { + state=eToBeReset; return; + } + if (selected.sv!=this && selected.sv->score==selected.sv->other->score) { + alert("other score is the same"); + state=eToBeReset; return; + } + if (key=='c') { + state=eCopying; ma->ctrl->set_rbut(ma->but_copy,false); + } + else { + state=eMoving; ma->ctrl->set_rbut(ma->but_move,false); + } + cur_point=prev_point=Point(x,y); + delta_lnr=delta_snr=left_snr=0; + if (selected.sv!=this) { + int lnr1,snr1, + min_snr; + SLList_elem *sd; + state= state==eMoving? eMoving_hor : eCopying_hor; + min_snr=selected.min_snr(); // find smallest snr + left_snr=cur_snr - min_snr; + for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // draw ghost notes + lnr1=sd->d.lnr; + snr1=sd->d.snr + left_snr; + if (lnr1>=0 && lnr1=0) { + if (score->check_len(snr1+1)) { + set_scroll_range(); draw_sc2(false); + } + drawS_ghost(this,snr1,lnr1,sd->d.sign,false); + } + } + } + return; + case 'p': // portamento + if (cat!=ePlay) { + state=eToBeReset; + return; + } + ma->ctrl->set_rbut(ma->but_porta,false); + sec=score->get_section(lnr,cur_snr); + if (sec->port_dlnr) { + sec->drawPortaLine(this,snr,lnr,true); // erase portaline + sec->port_dlnr=sec->port_dsnr=0; + state=eToBeReset; + } + else { + set_custom_cursor(scview->win,dot_cursor); + cur_point.set(x,y); + state=ePortaStart; + } + return; + case XK_Shift_L: + case 0: + break; + default: + alert("unexpected key pressed"); + state=eToBeReset; + return; + } + if (!app->sampl->value) { + if (cat==ePlay) state=eErasing; + else if (cat==eSilent) state=eTracking; + } + proto.reset(); + proto.cat=ePlay; + proto.s_col=app->act_color; + proto.s_group=score->ngroup; + proto.sign= draw_mode==ePiano ? sign : score->lin[lnr].n_sign; + proto.sampled=app->sampl->value; + proto.stacc= mouse_but==Button2; + switch (cat) { + case ePlay: + if (draw_mode==eTimingValue) { + if (!zoomed) { state=eIdle; return; } + sec=score->get_section(lnr,cur_snr); + switch(mouse_but) { + case Button1: + sec->del_start=(sec->del_start+1) % subdiv; + sec->drawSect(this,other,snr,lnr); + if (snr>0 && (sec-1)->cat==ePlay && (sec-1)->del_end) // if needed, redraw previous section + (sec-1)->drawSect(this,other,snr-1,lnr); + break; + case Button3: + sec->del_end=(sec->del_end+1) % subdiv; + if (sec->del_end==0 && snrlen-1) // redraw next section + (sec+1)->drawSect(this,other,snr+1,lnr); + sec->drawSect(this,other,snr,lnr); + if (snr>0 && (sec-1)->cat==ePlay && (sec-1)->del_end) // redraw previous section, if needed + (sec-1)->drawSect(this,other,snr-1,lnr); + break; + } + state=eIdle; + return; + } + sec=score->get_section(lnr,cur_snr); + if (multiple_note) { + int fst_sign=sec->sign; + sec=sec->get_the_section(&proto,0); // get equal or new section + int nxt_n=sec->nxt_note; + *sec=proto; + sec->nxt_note=nxt_n; // nxt_note not copied + sec->sign=fst_sign; // sign of 1st sec copied; + state=eToBeReset; + } + else if (mouse_but==Button2 && !sec->sampled) { // middle mouse button + bool stacc=!sec->stacc; + for (ScSection *sec1=sec;sec1;sec1=sec1->nxt()) sec1->stacc=stacc; + } + else { + if (sec->sel) selected.remove(lnr,snr); + if (sec->port_dlnr) { + sec->drawPortaLine(this,snr,lnr,true); + sec->port_dlnr=sec->port_dsnr=0; + } + sec->cat=eSilent; + } + break; + case eSilent: + if (draw_mode==eTimingValue && zoomed) { state=eIdle; return; } + *score->get_section(lnr,cur_snr)=proto; + break; + default: alert("sect cat %d?",cat); + } + score->get_section(lnr,cur_snr)->drawSect(this,other,snr,lnr); + if (sec->cat==eSilent) sec->reset(); // after drawSect() +} + +void ScoreView::mouseMoved(int x,int y,int mouse_but) { + if (!score) { state=eIdle; return; } + switch (state) { + case eTracking: + case eErasing: { + int snr, + new_snr=sectnr(x); + if (new_snr<=prev_snr) return; // only tracking to the right + for (snr=prev_snr+1;;++snr) { + if (snr<0 || snr>=score->len) { state=eIdle; return; } + ScSection *const sect=score->get_section(cur_lnr,snr); + if (state==eTracking) { + if (sect->cat==eSilent) { + *sect=*score->get_section(cur_lnr,cur_snr); + sect->drawSect(this,other,snr,cur_lnr); + } + else if (sect->cat==ePlay) { state=eIdle; break; } + } + else { + if (sect->cat==ePlay) { + sect->cat=eSilent; + sect->drawSect(this,other,snr,cur_lnr); + if (sect->sel) selected.remove(cur_lnr,snr); + if (sect->port_dlnr) { + sect->drawPortaLine(this,snr,cur_lnr,true); + sect->port_dlnr=sect->port_dsnr=0; + } + if (sect->stacc || sect->sampled) { state=eIdle; sect->reset(); break; } + sect->reset(); + } + else if (sect->cat==eSilent) { state=eIdle; break; } + } + if (snr==new_snr) break; + } + prev_snr=new_snr; + } + break; + case eMoving: + case eMoving_hor: + case eMoving_vert: + case eCopying: + case eCopying_hor: + case eCopying_vert: { + SLList_elem *sd; + int lnr1, snr1, + dx=(x-prev_point.x)/sect_len, + dy=(y-prev_point.y)/sclin_dist; + if (dy || dx) { + int x2=abs(dx), + y2=abs(dy); + if (state==eMoving) { + if (x2>y2) state=eMoving_hor; + else if (x2y2) state=eCopying_hor; + else if (x2nxt) { // erase old ghost notes + lnr1=sd->d.lnr + delta_lnr; + snr1=sd->d.snr + delta_snr + left_snr; + if (lnr1>=0 && lnr1=0 && snr1len) + drawS_ghost(this,snr1,lnr1,sd->d.sign,true); + } + int prev_delta_lnr=delta_lnr, + prev_delta_snr=delta_snr; + if (state==eMoving_vert || state==eCopying_vert) + delta_lnr=(y-cur_point.y)/sclin_dist; + else if (state==eMoving_hor || state==eCopying_hor) + delta_snr=(x-cur_point.x)/sect_len; + selected.check_direction(delta_lnr - prev_delta_lnr,delta_snr - prev_delta_snr); + for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // draw new ghost notes + lnr1=sd->d.lnr + delta_lnr; + snr1=sd->d.snr + delta_snr + left_snr; + if (lnr1>=0 && lnr1=0) { + if (score->check_len(snr1+1)) { + set_scroll_range(); draw_sc2(false); + } + drawS_ghost(this,snr1,lnr1,sd->d.sign,false); + } + } + } + } + break; + case eCollectSel: + case eCollectColSel: { + int snr=sectnr(x); + if (snr<0 || snr>=score->len) { state=eIdle; return; } + if (snr>prev_snr) + for (++prev_snr;;++prev_snr) { + if (state==eCollectColSel) sel_column_1col(prev_snr,app->act_color,app->sampl->value); + else sel_column(prev_snr); + if (prev_snr==snr) break; + } + else if (snract_color,app->sampl->value); + else sel_column(prev_snr); + if (prev_snr==snr) break; + } + } + break; + case ePortaStart: { + uchar sign; + int snr=sectnr(x); + const int lnr=linenr(y,snr,sign); + ScSection *const sect=score->get_section(lnr,snr); + if (sect->cat==ePlay) + set_custom_cursor(scview->win,dot_cursor); + else + set_custom_cursor(scview->win,cross_cursor); + } + break; + case eIdle: + case eToBeReset: + break; + default: + alert("mouse moving state %d?",state); + state=eIdle; + } +} + +void ScSection::set_to_silent(ScoreView *sv,int snr,int lnr) { + if (sel_color && nxt_note) { + *this=*nxt(); + sel=false; + drawSect(sv,sv->other,snr,lnr); + } + else { + cat=eSilent; + drawSect(sv,sv->other,snr,lnr); + reset(); + } +} + +int ScSection::copy_to_new_sect(int sctype,uint *lst_cp) { // supposed: this = *nxt() + ScSection *sec, + *to=0; + int index=0, + ind; + for (sec=this;sec;sec=sec->nxt()) { + if (!(ind=mn_buf.new_section(sctype))) return 0; + if (to) to->nxt_note=ind; + else index=ind; + to=mn_buf.buf+ind; + *to=*sec; + if (lst_cp) *lst_cp=ind; + } + return index; +} + +void ScSection::rm_duplicates(bool *warn) { + ScSection *sec,*sec2; + if (warn) *warn=false; + for (sec=this;sec;sec=sec->nxt()) { + sec2=sec->nxt(); + if (!sec2) break; + for (;sec2;sec2=sec2->nxt()) { + if (sec->s_col==sec2->s_col && + sec->s_group==sec2->s_group && + sec->sampled==sec2->sampled) { + if (warn) { *warn=true; return; } + sec->nxt_note=sec2->nxt_note; + } + } + } +} + +void ScoreView::mouseUp(int x,int y,int mouse_but) { + static MouseAction *ma=app_local->mouseAction; + switch (state) { + case eMoving_hor: + case eMoving_vert: + case eCopying_hor: + case eCopying_vert: { + SLList_elem *sd; + ScSection *sec, + *from,*to; + int lnr1, snr1, + to_lnr, to_snr; + for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // erase ghost notes + lnr1=sd->d.lnr + delta_lnr; + snr1=sd->d.snr + delta_snr + left_snr; + if (lnr1>=0 && lnr1=0 && snr1len) + drawS_ghost(this,snr1,lnr1,sd->d.sign,true); + } + if (key_pressed!='k') { // not keep last score + if (delta_lnr || delta_snr || selected.sv!=this) { + selected.check_direction(delta_lnr,delta_snr); + for (sd=selected.sd_list.lis;sd;) { + to_lnr=sd->d.lnr + delta_lnr; + to_snr=sd->d.snr + delta_snr + left_snr; + from=selected.sv->score->get_section(sd->d.lnr,sd->d.snr); + if (to_lnr>=0 && to_lnr=0) { + if (score->check_len(to_snr+1)) { + set_scroll_range(); draw_sc2(false); + } + to=score->get_section(to_lnr,to_snr); + ScSection from1(*from); + if (from->sel_color) from1.nxt_note=0; + for (sec=&from1;sec;sec=sec->nxt()) { + sec->s_group=score->ngroup; // maybe copied from different score + if (state==eMoving_vert || state==eCopying_vert) // sign is not copied + sec->sign=score->lin[to_lnr].n_sign; + } + if (!to->prepend(&from1,0,0)) return; + to->rm_duplicates(); + + if (state==eMoving_hor || state==eMoving_vert) { + if (from->port_dlnr) from->drawPortaLine(selected.sv,sd->d.snr,sd->d.lnr,true); + from->set_to_silent(selected.sv,sd->d.snr,sd->d.lnr); + } + else { + from->sel=false; + from->drawSect(selected.sv,selected.sv->other,sd->d.snr,sd->d.lnr); + } + sd->d.lnr=to_lnr; + sd->d.snr=to_snr; + sd->d.sign=to->sign; + if (to_lnr>=0 && to_lnrdrawSect(this,other,to_snr,to_lnr); + if (to->port_dlnr) to->drawPortaLine(this,to_snr,to_lnr,false); + } + sd=sd->nxt; + } + else { + if (state==eMoving_hor || state==eMoving_vert) { + from->set_to_silent(selected.sv,sd->d.snr,sd->d.lnr); + } + else { + from->sel=false; + from->drawSect(selected.sv,selected.sv->other,sd->d.snr,sd->d.lnr); + } + sd=selected.sd_list.remove(sd); + } + } + } + if (state==eCopying_hor || state==eMoving_hor) { // update endline? + int max_snr=0; + for (sd=selected.sd_list.lis;sd;sd=sd->nxt) // find highest snr + if (max_snrd.snr) max_snr=sd->d.snr; + if (score->end_sect && max_snr>=score->end_sect) { + draw_endline(false); + if (other->score==score) other->draw_endline(false); + score->end_sect=max_snr+1; + draw_endline(true); + if (other->score==score) other->draw_endline(true); + } + } + selected.sv=this; + } + } + ma->reset(); + break; + case ePortaStart: + ma->reset(); + set_custom_cursor(scview->win,up_arrow_cursor); + { uchar sign; + int snr=sectnr(x); + const int lnr=linenr(y,snr,sign); + if (lnr-cur_lnr==0 && snr-cur_snr==0) { alert("portando: same note"); break; } + if (abs(lnr-cur_lnr)>= 1<<5) { alert("portando notes height difference >= %d",1<<5); break; } + if (snr-cur_snr<1) { alert("portando notes distance < 0"); break; } + if (snr-cur_snr>= 1<<5) { alert("portando notes distance >= %d",1<<5); break; } + ScSection *const sect=score->get_section(lnr,cur_snr); + ScSection *sec; + for (sec=score->get_section(lnr,snr);sec;sec=sec->nxt()) { + if (sec->cat==ePlay && !sec->sampled && sect->s_col==sec->s_col) + break; + } + if (!sec) { alert("portando note mismatch"); break; } + sect->port_dlnr= lnr-cur_lnr; + sect->port_dsnr= snr-cur_snr-1; + sect->drawPortaLine(this,cur_snr,cur_lnr,false); + } + break; + case eCollectSel: + case eCollectColSel: + case eIdle: + case eTracking: + case eErasing: + break; + case eMoving: + case eCopying: + case eToBeReset: + if (keep_m_action) keep_m_action=false; + else ma->reset(); + break; + default: + alert("mouse-up state %d?",state); + } + state=eIdle; +} + +ScoreView::ScoreView(Rect rect,int ind): + ScoreViewBase(rect,0), + index(ind) { + mnrview=new BgrWin(top_win->win,Rect(rect.x+s_wid,rect.y,scview_wid-s_wid,mnr_height), + FR,draw_cmd,mouse_down,0,0,cBgrGrey,Id('scmn',index)); + set_custom_cursor(mnrview->win,text_cursor); + scview=new BgrWin(top_win->win,Rect(rect.x+s_wid,rect.y+mnr_height,scview_wid-s_wid,rect.height-scrollbar_wid-mnr_height), + FR,draw_cmd,mouse_down,mouse_moved,mouse_up,cWhite,Id('scv',index)); + set_custom_cursor(scview->win,up_arrow_cursor); + signsview=new BgrWin(top_win->win,Rect(rect.x,rect.y+mnr_height,s_wid-1,rect.height-mnr_height-scrollbar_wid), + FN,drawsigns_cmd,cBgrGrey,1,Id(0,index)); + + chord_name=new BgrWin(scview->win, + Rect(20,rect.height-scrollbar_wid-mnr_height-TDIST-1,200,TDIST), + MR,draw_chn,cBackground,1,Id(0,index)); + chord_name->hide(); + + scroll=new HScrollbar(top_win->win,Rect(rect.x+s_wid,rect.y+rect.height-scrollbar_wid+1,scview_wid-s_wid,0), + Style(1,0,sect_length),FR,sect_scv*sect_len+10,scroll_cmd,Id(0,index)); + + text_win=new BgrWin(top_win->win,Rect(rect.x+scview_wid+1,rect.y,rect.width-scview_wid,rect.height+1), + MR,0,cForeground); + + sc_name=new TextWin(text_win->win,Rect(2,2,rect.width-scview_wid-6,0),FN,1); + + group_nr=new RButWin(text_win,Rect(58,32,18,3*TDIST),FN,"gr#",false,rbutwin_cmd,cForeground,Id('grnr',index)); + for (int i=0;i<3;++i) + group_nr->add_rbut(i==0?"0":i==1?"1":"2"); + + zoom=new CheckBox(text_win->win,Rect(2,42,0,15),FN,cForeground,"zoom",zoom_cmd,Id(0,index)); + + set_repeat=new CheckBox(text_win->win,Rect(2,59,0,15),FN,cForeground,"repeat",0,Id(0,index)); + + play_1col=new CheckBox(text_win->win,Rect(2,76,0,15),FN,cForeground,"play 1 color",0,Id(0,index)); + + display_mode=new RButWin(text_win,Rect(2,110,rect.width-scview_wid-6,4*TDIST),FN,"display",false,display_cmd,cForeground,Id(0,index)); + display_mode->add_rbut("instruments"); + display_mode->add_rbut("timing"); + display_mode->add_rbut("accidentals"); + display_mode->add_rbut("white keys"); + + new Button(text_win->win,Rect(2,187,33,0),FN,"play",svplay_cmd,Id(0,index)); + + ext_rbut_style.bgcol=cBackground; + active=app->active_scoreCtrl->add_extrbut(text_win->win,Rect(38,174,42,0),FN,"active",Id(0,index)); + ext_rbut_style=def_erb_st; + + the_chord_name[0]=0; +} + +void ScSection::drawPlaySect(ScoreViewBase *theV,Point start,Point end,uchar n_sign) { + uint s_color= nxt_note ? eGrey : s_col; + int mid=end.x; + if (theV->zoomed) { + if (del_start) + start.x += del_start*sect_length; + if (del_end) + end.x += del_end*sect_length; + } + switch (theV->draw_mode) { + case eColorValue: + case ePiano: { + static uint sel_bgcolor[eGrey+1]= { // selected note background color + cGrey,cGrey,cGrey,cWhite,cGrey,cWhite,cWhite }; + if (sel) theV->scview->set_selected(true,sel_bgcolor[s_color]); + set_color(col2color(s_color)); + } + break; + case eTimingValue: + if (del_start) set_color(cRed); + else if (del_end && !theV->zoomed) set_color(cBlue); + else set_color(cDarkGrey); + break; + case eAccValue: + if (!sampled && n_sign!=sign) { + switch (n_sign) { + case 0: set_color(sign==eHi ? cRed : cBlue); break; + case eHi: set_color(sign==0 ? cBlack : cBlue); break; + case eLo: set_color(sign==0 ? cBlack : cRed); break; + } + } + else set_color(cGrey); + break; + } + if (sampled) { + if (theV->draw_mode!=ePiano) { + set_width(1); + theV->scview->draw_line(Point(start.x,start.y-2),Point(start.x+6,start.y+1)); + theV->scview->draw_line(Point(start.x,start.y-1),Point(start.x+6,start.y+2)); + theV->scview->draw_line(Point(start.x,start.y+1),Point(start.x+6,start.y-2)); + theV->scview->draw_line(Point(start.x,start.y+2),Point(start.x+6,start.y-1)); + } + if (sign) { + set_color(cWhite); + if (sign==eHi) theV->scview->fill_rectangle(Rect(start.x+3,start.y-2,3,2)); + else if (sign==eLo) theV->scview->fill_rectangle(Rect(start.x+3,start.y+1,3,2)); + } + } + else { + if (stacc) end.x-=2; + set_width(3); + if (theV->zoomed && theV->draw_mode==eTimingValue && del_end) { + theV->scview->draw_line(start,Point(mid,start.y)); + set_color(cBlue); + theV->scview->draw_line(Point(mid,start.y),end); + } + else + theV->scview->draw_line(start,end); + if (sign && theV->draw_mode!=ePiano) { + set_width_color(1,cWhite); + start.x+=2; + if (sign==eHi) { --start.y; --end.y; } + else if (sign==eLo) { ++start.y; ++end.y; } + theV->scview->draw_line(start,end); + } + } + if (sel) theV->scview->set_selected(false,0); // reset line style +} + +void ScSection::drawSect(ScoreViewBase* theV,int snr,int lnr) { + const int x=(snr-theV->leftside)*theV->sect_len, + y= theV->draw_mode==ePiano ? ypos(lnr,sign,theV->piano_y_off) : ypos(lnr); + const uchar n_sign=theV->score->lin[lnr].n_sign; + Point start(x,y), + end(x+theV->sect_len,y); + set_width_color(theV->draw_mode==ePiano ? 3 : 5,cWhite); + theV->scview->draw_line(start,end); + switch (cat) { + case eSilent: + --end.x; + if (theV->draw_mode==ePiano) { + uint col=theV->p_lnr2col(lnr,sign); + if (col) { + set_width_color(1,col); + theV->scview->draw_line(start,end); + } + } + else { + set_width_color(1,linecol.col[lnr]); + theV->scview->draw_line(start,end); + if (lnr%2==0) { + int meter=theV->score->sc_meter; + if (!meter) meter=app->act_meter; + if (snr%meter==0) { // timing marks between lines + set_color(cGrey); + theV->scview->draw_line(Point(x,y-1),Point(x,y+1)); + } + } + } + break; + case ePlay: + drawPlaySect(theV,start,end,n_sign); + break; + default: alert("section cat %d?",cat); + } +} + +void ScSection::drawSect(ScoreViewBase* theV,ScoreViewBase* otherV,int snr,int lnr) { + drawSect(theV,snr,lnr); + if (otherV->score==theV->score) drawSect(otherV,snr,lnr); +} + +void ScSection::drawPortaLine(ScoreViewBase *theV,int snr,int lnr,bool erase) { + int x1=(snr-theV->leftside+1)*theV->sect_len, + y1=theV->draw_mode==ePiano ? ypos(lnr,sign,theV->piano_y_off) : ypos(lnr), + x2=x1+port_dsnr*theV->sect_len, + y2=theV->draw_mode==ePiano ? ypos(lnr+port_dlnr,0,theV->piano_y_off) : ypos(lnr+port_dlnr); + // piano mode: y2 incorrect if sign != 0 + Point start(x1,y1), + end(x2,y2); + set_width(1); + if (erase) set_color(cWhite); + else set_color(col2color(s_col)); + theV->scview->draw_line(start,end); +} + +void ScoreView::draw_endline(bool draw_it) { + if (!score->end_sect) return; + int x=(score->end_sect-leftside)*sect_len; + if (draw_it) set_width_color(1,cBlack); + else set_width_color(1,cWhite); + scview->draw_line(Point(x,ypos(17)),Point(x,ypos(25))); + if (!draw_it && draw_mode!=ePiano && score->end_sectlen) // restore sections at old endline + for (int n=17;n<25;++n) + score->get_section(n,score->end_sect)->drawSect(this,score->end_sect,n); +} + +void ScoreViewBase::draw_endline(bool) { + if (!score->end_sect) return; + int x=(score->end_sect-leftside)*sect_len; + set_width_color(1,cBlack); + scview->draw_line(Point(x,ypos(17)),Point(x,ypos(25))); +} + +void ScoreView::draw_cmd(Id id) { + ScoreView *sv=the_sv(id.id2); + sv->set_scroll_range(); // needed after resizing + sv->draw_sc2(false); +} + +void ScoreView::drawsigns_cmd(Id id) { the_sv(id.id2)->drawSigns(); } + +void ScoreView::drawSigns() { + if (!score) return; + static const char + *sharp[]={ + "5 6 2 1", + "# c #000000", + ". c #ffffff", + "..#.#", + "#####", + ".#.#.", + ".#.#.", + "#####", + "#.#.." + }, + *flat[]={ + "5 6 2 1", + "# c #000000", + ". c #ffffff", + ".#...", + ".#...", + ".###.", + ".#..#", + ".#.#.", + ".##.." + }; + static Pixmap + sharp_pm=create_pixmap(sharp).pm, + flat_pm=create_pixmap(flat).pm; + int lnr, + y; + uint sign; + signsview->clear(); + for (lnr=0;lnrlin[lnr].n_sign; + if (sign) + draw_pixmap(signsview->win,Point((lnr%7 & 1)==0 ? 2 : 7,y),sign==eHi ? sharp_pm : flat_pm,5,6); + } +} + +void ScoreViewBase::draw_scview(int start_snr,int stop_snr,int meter) { // draw_mode != ePiano + int lnr, snr, + x1, y1; + Point start, + end; + uint line_color; + for (lnr=0;lnrget_section(lnr,0), + *sec; + for (snr=start_snr;snrlen && snrdraw_line(start,end); + } + if (snr%meter==0 && lnr%2==0) { // timing marks between lines + set_color(cGrey); + scview->draw_line(Point(x1,y1-1),Point(x1,y1+1)); + } + } + for (snr=leftside;snrlen && snrcat==ePlay && (snr>=start_snr || sec->port_dlnr)) { + start.set(x1,y1); + end.set(x1+sect_len,y1); + sec->drawPlaySect(this,start,end,score->lin[lnr].n_sign); + for (ScSection *sec1=sec;sec1;sec1=sec1->nxt()) { + if (sec1->port_dlnr) + sec1->drawPortaLine(this,snr,lnr,false); + } + } + } + } +} + +void ScoreViewBase::p_draw_scview(int start_snr,int stop_snr,int meter) { // draw_mode = ePiano + int lnr,snr, + x1,y1, + diff= scale_dep==eScDep_lk ? keynr2ind(app->chordsWin->the_key_nr) : + scale_dep==eScDep_sk ? keynr2ind(score->sc_key_nr) : + 0; + Point start,end; + set_width_color(1,cGrey); + for (snr=start_snr;snrlen && snrdraw_line(Point(x1,y_off),Point(x1,(piano_lines_max-1) * p_sclin_dist + piano_y_off)); + } + } + for (int nr=0;nrlen && snrdraw_line(start,end); + } + } + } + for (lnr=0;lnrget_section(lnr,0), + *sec; + for (snr=leftside;snrlen && snrcat==ePlay && (snr>=start_snr || sec->port_dlnr)) { + x1=(snr-leftside) * sect_len; + y1=ypos(lnr,sec->sign,piano_y_off); + start.set(x1,y1); + end.set(x1+sect_len,y1); + sec->drawPlaySect(this,start,end,score->lin[lnr].n_sign); + for (ScSection *sec1=sec;sec1;sec1=sec1->nxt()) { + if (sec1->port_dlnr) + sec1->drawPortaLine(this,snr,lnr,false); + } + } + } + } +} + +void ScoreViewBase::draw_sc2(bool clear,int delta) { + if (!score) return; + int smax=scview->dx/sect_len, + start_snr,stop_snr; + if (delta<=-smax || delta>=smax) { + delta=0; clear=true; // else wrong results + } + if (delta<0) { + start_snr=leftside+smax+delta; + stop_snr=leftside+smax; + } + else if (delta>0) { + start_snr=leftside; + stop_snr=leftside+delta; + } + else { + if (clear) { scview->clear(); mnrview->clear(); } + start_snr=leftside; + stop_snr=leftside+smax; + } + int meter= score->sc_meter==0 ? app->act_meter : score->sc_meter; + ScoreView *sv=static_cast(this); + MusicView *mv=static_cast(this); + if (draw_mode==ePiano) { + p_draw_scview(start_snr,stop_snr,meter); + if (sv_type==eMusic) + mv->draw_tune_names(); + else { + sv->signsview->hide(); + if (sv->the_chord_name[0]) sv->chord_name->map(); + } + } + else { + draw_scview(start_snr,stop_snr,meter); + if (sv_type==eMusic) + mv->draw_tune_names(); + else { + sv->signsview->map(); sv->drawSigns(); + sv->chord_name->hide(); + } + } + draw_start_stop(score); + draw_endline(true); +} + +void ScoreView::assign_score(Score *sc,bool draw_it) { + score=sc; + group_nr->set_rbutnr(sc->ngroup,false); + sc_name->print_text(tnames.txt(sc->name)); + play_start=0; play_stop=-1; leftside=0; + scroll->value=0; + if (draw_it) { + set_scroll_range(); + draw_sc2(true); + } +} + +void ScoreView::modify_sel(int mes) { + SLList_elem *sd; + ScSection *sect; + int shift=0, // shift>0 -> midinr increase, linenr decrease + mnr; + bool warn=false; + switch (mes) { + case 'shmu': case 'shmd': + case 'shcu': case 'shcd': + shift=app_local->mouseAction->semi_tones->value; + if (mes=='shmd' || mes=='shcd') shift= -shift; + selected.check_direction(-shift,0); + break; + } + for (sd=selected.sd_list.lis;sd;) { + sect=score->get_section(sd->d.lnr,sd->d.snr); + switch (mes) { + case 'uns': + sect->sel=false; + break; + case 'rcol': + if (sect->sel_color || !sect->nxt_note) { + sect->s_col=app->act_color; + if (!sect->sampled && app->sampl->value) { + sect->sampled=true; + } + else if (sect->sampled && !app->sampl->value) { + sect->sampled=false; + } + } + else + alert("trying to re-color multiple note in measure %d",score->get_meas(sd->d.snr)); + break; + case 'del ': + if (sect->port_dlnr) + sect->drawPortaLine(this,sd->d.snr,sd->d.lnr,true); + if (sect->sel_color && sect->nxt_note) + *sect=*sect->nxt(); + else + sect->cat=eSilent; // reset() later + break; + case 'shmu': case 'shmd': // shift move + case 'shcu': case 'shcd': { // shift copy + bool move= mes=='shmu' || mes=='shmd'; + mnr=lnr_to_midinr(sd->d.lnr,sd->d.sign) + shift; + uchar lnr,sign; + if (midinr_to_lnr(mnr,lnr,sign,score->signs_mode)) { + ScSection *to=score->get_section(lnr,sd->d.snr); + ScSection from1(*sect); + if (sect->sel_color) from1.nxt_note=0; + if (move) { + sect->set_to_silent(this,sd->d.snr,sd->d.lnr); + } + else { + sect->sel=false; + sect->drawSect(this,other,sd->d.snr,sd->d.lnr); + } + if (!to->prepend(&from1,0,0)) return; + to->rm_duplicates(); + sd->d.sign=to->sign=sign; + sd->d.lnr=lnr; + sect=to; + } + else { + warn=true; + sect->sel=false; + sect->drawSect(this,other,sd->d.snr,sd->d.lnr); + sd=selected.sd_list.remove(sd); + continue; + } + } + break; + } + sect->drawSect(this,other,sd->d.snr,sd->d.lnr); + if (sect->cat==eSilent) sect->reset(); // after drawSect()! + sd=sd->nxt; + } + switch (mes) { + case 'del ': + case 'uns': + selected.reset(); + draw_endline(true); + break; + case 'shmu': case 'shmd': if (warn) alert("warning: some notes not shifted"); break; + case 'shcu': case 'shcd': if (warn) alert("warning: some notes not copied"); break; + } +} + +bool eq(char *word,int colnr,const char *col,const char *param,const char *alt_param,char *&s) { +// word="attack", color_name[colnr]="red", param="attack", alt_param="at" + static char sbuf[40]; // used for error messages + s=0; + if (!strcmp(color_name[colnr],col) && ((alt_param && !strcmp(word,alt_param)) || !strcmp(word,param))) { + snprintf(sbuf,40,"%s %s",color_name[colnr],param); + s=sbuf; + return true; + } + return false; +} + +bool eq(char *word,int colnr,const char *col,const char *param_0,const char *param_1,const char *param_2,int &p_nr,char *&s) { +// word="ampl-0", color_name[colnr]="black", param_0="ampl-0", param_1="ampl-1", param_2="ampl-2" + static char sbuf[40]; // used for error messages + s=0; p_nr=0; + if (!strcmp(color_name[colnr],col) && + (!strcmp(word,param_0) || (!strcmp(word,param_1) && (p_nr=1)) || (!strcmp(word,param_2) && (p_nr=2)))) { + snprintf(sbuf,40,"%s %s",color_name[colnr],param_0); + s=sbuf; + return true; + } + return false; +} + +bool App::save(const char *fname) { + FILE *tunes; + int i; + Encode enc; + Score *scp; + ScSection *sec,*sect, + *lst_sect; + int scorenr, + snr,lnr; + bool warn_dupl=false, + warn; + if ((tunes=fopen(fname,"w"))==0) { + alert("file '%s' not writable",fname); + return false; + } + fputs(enc.set_meter(act_meter),tunes); + for (scorenr=0;scorenr<=scores.lst_score;++scorenr) { + scp=scores[scorenr]; + int met=scp->sc_meter; + if (met==act_meter) met=0; // end_sect = default + fprintf(tunes,"\n%s \"%s\" ", + enc.score_start4(scorenr,met,scp->end_sect,scp->ngroup,scp->sc_key_nr),tnames.txt(scp->name)); + for (snr=0;snrlen-1 && (snr==0 || scp->end_sect!=snr);++snr) { // 1 extra for end + for (lnr=0;lnrget_section(lnr,snr); + switch (sect->cat) { + case ePlay_x: + case ePlay: + if (sect->nxt_note) { + sect->rm_duplicates(&warn); // else same_color() erroneous + if (warn) { + alert("duplicate multiple note in %s (meas nr %d)", + tnames.txt(scp->name), + snr/(scp->sc_meter==0 ? app->act_meter : scp->sc_meter)); + warn_dupl=true; + } + } + for (sec=sect;sec;sec=sec->nxt()) { + if (sec->cat==ePlay) { + i=same_color(sect,sec,scp->get_section(lnr,0)+scp->len,lst_sect); + fputs(enc.play_note(lnr,snr,i,sec->sign, + sec->sampled ? 2 : lst_sect->stacc ? 1 : 0, + sec->s_col,sec->s_group, + lst_sect->port_dlnr, + lst_sect->port_dsnr, + sec->del_start,lst_sect->del_end),tunes); + } + } + break; + case eSilent: + break; + default: + alert("save cat=%u?",sect->cat); + } + } + } + restore_marked_sects(scp,0); + } + putc('\n',tunes); // Unix tools need this + fclose(tunes); + if (warn_dupl) alert("Repair duplicates, then save again!"); + return true; +} + +bool App::read_tunes(const char *fname,bool add_tunes) { + static TunesView *tv=app_local->tunesView; + int fst_scnr; + if (add_tunes) { + fst_scnr=scores.lst_score+1; + if (!restore(fname,true)) + return false; + } + else { + fst_scnr=0; + tv->rbutwin->empty(); + if (!restore(fname,false)) { // sets scores.lst_score + for (int n=0;n<2;++n) the_sv(n)->reset(); + return false; + } + } + for (int n=fst_scnr;n<=scores.lst_score;++n) { + scores[n]->rbut=tv->rbutwin->add_rbut(tnames.txt(scores[n]->name)); + if (!add_tunes && n<2) + the_sv(n)->assign_score(scores[n],true); + } + tv->scroll->value=0; tv->set_scroll_range(); + return true; +} + +Score *new_score(const char *name) { + Score *sc=scores.new_score(name); + TunesView *tv=app_local->tunesView; + sc->rbut=tv->rbutwin->add_rbut(tnames.txt(sc->name)); + tv->set_scroll_range(); + return sc; +} + +void App::new_tune(const char *tname) { + Score *sc=new_score(tname); + dia_wd->dlabel("tune added",cForeground); + app_local->info_view->set_modif(eSco,true); + if (act_score>nop) { + ScoreView *sv=the_sv(act_score); + if (selected.sv==sv) selected.restore_sel(); + sv->assign_score(sc,true); + check_reset_as(); + } +} + +Score* Scores::exist_name(const char *nam) { + if (!nam || !nam[0]) { alert("null name"); return 0; } + for (int i=0;i<=lst_score;++i) { + if (!strcmp(tnames.txt(buf[i]->name),nam)) return buf[i]; + } + return 0; +} + +void Score::copy(Score *from) { // name and group NOT copied + // group, end_sect, sc_meter, amplitude and color copied + int snr,lnr, + stop; + end_sect=from->end_sect; + sc_meter=from->sc_meter; + if (end_sect) { + check_len(end_sect+1); stop=end_sect; + } + else { + stop=from->len; check_len(stop); + } + for (lnr=0;lnrlin[lnr].n_sign; + for (snr=0;snrget_section(lnr,snr); + to->sel=false; + } + } + ngroup=from->ngroup; + signs_mode=from->signs_mode; + sc_key_nr=from->sc_key_nr; +} + +void Score::add_copy(Score *sc,int start,int stop,int tim, + int shift,int raise,ScoreViewBase* theV,int atcolor) { + // name, nampl NOT copied + int snr,snr1, + lnr; + uchar lnr1, + sign; + ScSection from,*sec, + *to; + check_len(tim+stop-start); + if (sc_type==eMusic) { + ScInfo info(eText); + info.text=tnames.txt(sc->name); + scInfo[tim].add(info); + } + for (snr=start,snr1=tim-1;snrget_section(lnr,snr); + if (from.cat==ePlay) { + if (raise || shift) { + if (raise) { + lnr1=lnr-raise; + sign=sc->lin[lnr1].n_sign; // sign of to-linenr of from-score + } + if (shift) { + if(!midinr_to_lnr(lnr_to_midinr(lnr,from.sign)+shift,lnr1,sign,signs_mode)) continue; + } + } + else { lnr1=lnr; sign=from.sign; } + to=get_section(lnr1,snr1); + + uint lst_cp=0; + if (!to->prepend(&from,sc_type,&lst_cp)) return; + to->sel=false; + to->sign=sign; + if (atcolor>=0) to->s_col=atcolor; + for (sec=to;;sec=sec->nxt()) { + if (sc_type==eMusic) // assign tune name + sec->src_tune= sc->namename : tn_max-1; + else if (sc_type!=eScorebuf) // assign group + sec->s_group=ngroup; + if (!lst_cp || sec->nxt_note==lst_cp) break; + } + to->rm_duplicates(); // after group or tune name assign! + } + } + } + if (snr1>lst_sect) { + end_sect=snr1+1; + check_len(end_sect+1); + lst_sect=snr1; + } +} + +void MusicView::draw_tune_name(int snr) { + int x=(snr - leftside) * sect_len, + y; + if (x>scview->dx) + return; + ScInfo* sci; + for (sci=score->scInfo+snr,y=12;sci;sci=sci->next) + if (sci->tag==eText) { + xft_draw_string(tnview->xft_win,xft_Black,Point(x,y),sci->text); + y+=10; + } +} + +void App::copy_tune(const char *tname) { + if (!act_tune) return; + Score *sc=scores.exist_name(tname); + ScoreView *sv; + if (sc) { + sc->reset(true); + sc->copy(act_tune); + if (app->find_score(sc,sv)) { + sv->group_nr->set_rbutnr(sc->ngroup,false); + sv->set_scroll_range(); + sv->draw_sc2(true); + } + } + else { + sc=new_score(tname); + sc->copy(act_tune); + } + if (act_score>nop) { + sv=the_sv(act_score); + if (selected.sv==sv) selected.restore_sel(); + sv->assign_score(sc,true); + check_reset_as(); + } + app_local->info_view->set_modif(eSco,true); +} + +bool App::find_score(Score* sc,ScoreView*& sv) { + for (int n=0;n<2;++n) { + sv=the_sv(n); + if (sv->score && sv->score==sc) + return true; + } + return false; +} + +AppLocal::AppLocal() { + Rect rect(0,0,0,0), + rect2(0,0,0,0); + + mouseAction=new MouseAction(); + + rect.set(sced_wid+4,0,s_wid+sect_scv*sect_length+score_info_len+2,scview_vmax); + + scViews[0]=new ScoreView(rect,0); + rect.y+=scview_vmax+4; + scViews[1]=new ScoreView(rect,1); + rect.y+=scview_vmax+4; + for (int n=0;n<2;n++) + if (n<=scores.lst_score) scViews[n]->assign_score(scores[n],false); + scViews[0]->other=scViews[1]; + scViews[1]->other=scViews[0]; + + rect.set(2,rect.y,sect_mus_max*sect_length,scview_vmax+tnview_height+1); + musicView=new MusicView(rect); + + rect.set(view_hmax-72,24,68,0); + new Button(top_win->win,rect,MR,"new tune...",dial_cmds.new_cmd); + + rect.y+=but_dist; + new Button(top_win->win,rect,MR,"copy tune...",dial_cmds.copy_cmd); + + rect.y+=but_dist; + new Button(top_win->win,rect,MR,"rename...",dial_cmds.rename_cmd); + + rect.y+=but_dist; + new Button(top_win->win,Rect(rect.x,rect.y,18,0),MR,arrow_up,mvup_cmd,Id('up')); + new Button(top_win->win,Rect(rect.x+22,rect.y,18,0),MR,arrow_down,mvdo_cmd,Id('do')); + new Button(top_win->win,Rect(rect.x+44,rect.y,18,0),MR,cross_sign,remove_cmd); + + rect.y+=but_dist; + new Button(top_win->win,rect,MR,"clear tune",clear_cmd); + + rect.y+=but_dist; + new Button(top_win->win,rect,MR,"mod times",modt_cmd); + + rect.y+=but_dist; + new Button(top_win->win,Rect(rect.x,rect.y,44,rect.height),MR,"cmd...",dial_cmds.cmd_cmd); + + rect.y+=but_dist; + new Button(top_win->win,rect,MR,"run script",run_script_cmd); + + rect.y+=but_dist; rect.width=36; + new Button(top_win->win,rect,MR,"play",mplay_cmd); + + rect.y+=but_dist; + new Button(top_win->win,rect,MR,"stop",stop_cmd); + + menus=new Menus(Rect(view_hmax-rbutview_left,0,rbutview_left-2,20)); + + rect.set(view_hmax-rbutview_left,TDIST+24,100,110); + tunesView=new TunesView(rect); + + rect2.set(rect.x+rect.width+5,TDIST+24,66,slider_height); + meterView=new MeterView(rect2); + + rect2.y=rect2.y+rect2.height+13; + tempoView=new TempoView(rect2); + + rect2.set(rect2.x+10,rect2.y+rect2.height+14,50,colors_max*TDIST); + colorView=new ColorView(rect2); + + rect.set(rect.x,rect.y+rect.height+4,0,15); + button_style.set(1,cBackground,0); + new Button(top_win->win,rect,MR,"chords window",chords_cmd); + button_style=def_but_st; + + rect.y+=checkbox_height; + app->sampl=new CheckBox(top_win->win,rect,MR,cBackground,"sampled notes",raw_cmd); + + rect.y+=checkbox_height; + app->no_set=new CheckBox(top_win->win,rect,MR,cBackground,"ignore set cmd's",0); + + rect.y=rect2.y+rect2.height+2; + app->conn_mk=new CheckBox(top_win->win,rect,MR,cBackground,"connect midi keyboard",con_mk_cmd); + + rect.set(view_hmax-rbutview_left,rect.y+18,rbutview_left-2,34); + textView=new Question(rect); + + rect.set(rect.x,rect.y+38,rect.width,ictrl_height); + app->act_instr_ctrl=app->black_control=new FMCtrl(rect,eBlack); + (app->red_control=new RedCtrl(rect,eRed))->cview->hide(); + (app->green_control=new GreenCtrl(rect,eGreen))->cview->hide(); + (app->blue_control=new BlueCtrl(rect,eBlue))->cview->hide(); + (app->brown_control=new FMCtrl(rect,eBrown))->cview->hide(); + (app->purple_control=new PurpleCtrl(rect,ePurple))->cview->hide(); + + (app->black_phm_control=new PhmCtrl(rect,eBlack))->cview->hide(); + (app->red_phm_control=new PhmCtrl(rect,eRed))->cview->hide(); + (app->green_phm_control=new PhmCtrl(rect,eGreen))->cview->hide(); + (app->blue_phm_control=new PhmCtrl(rect,eBlue))->cview->hide(); + (app->brown_phm_control=new PhmCtrl(rect,eBrown))->cview->hide(); + (app->purple_phm_control=new PhmCtrl(rect,ePurple))->cview->hide(); + + rect.y += ictrl_height+4; + app->scopeView=new ScopeView(Rect(rect.x,rect.y,scope_dim,68)); + + revb_view=new ReverbView(Rect(rect.x+171,rect.y,72,68)); + + rect.y+=71; + info_view=new InfoView(Rect(rect.x,rect.y,rect.width,76)); + + mv_display=new RButWin(info_view->bgwin,Rect(196,32,47,3*TDIST),FN,"display",false,mv_display_cmd,cBackground); + mv_display->add_rbut("instr"); + mv_display->add_rbut("timing"); + mv_display->add_rbut("w.keys"); + + rect.y+=78; + editScript=new EditScript(Rect(rect.x,rect.y,view_hmax-rect.x-2,view_vmax-rect.y-2)); +} + +App::App(char *inf): + act_tune(0), + cur_score(0), + input_file(inf), + act_score(nop), + act_color(cBlack), + act_meter(8), + act_action(0), + act_tune_ind(0), + act_tempo(11), + mv_key_nr(0), + nupq(4), + task(0), + stop_requested(false), + repeat(false), + chordsWin(0) { + app=this; + + active_scoreCtrl=new ExtRButCtrl(ScoreView::active_cmd); + scoreBuf=new Score(0,sect_scv,eScorebuf); + if (inf) { + modify_cwd(input_file); + char *s=input_file.get_ext(); + if (s && !strcmp(s,".scr")) { + script_file.cpy(input_file.s); + input_file.new_ext(".sco"); + if (!restore(input_file.s,false)) inf=0; + } + else if (!restore(input_file.s,false)) inf=0; + } + else { + if (!no_def_tunes) { + scores.new_score("tune1"); + scores.new_score("tune2"); + } + } + if (output_port==eJack) { + if ((jack_interface=new JackInterf("Amuc",play,'done',i_am_playing))->okay) { + if (jack_interface->buf_size) + IBsize=jack_interface->buf_size; + if (jack_interface->sample_rate) { + SAMPLE_RATE=jack_interface->sample_rate; + } + } + else { + jack_interface=0; + alert("Please start jackd and restart amuc"); + } + } + set_time_scale(); + set_buffer_size(); + app_local=new AppLocal(); + if (inf) { + app_local->info_view->txt[eSco]->print_text(input_file.s); + } + if (!init_phmbuf()) return; // physical models + if (script_file.s[0]) { + if (read_script(script_file.s)) { + app_local->info_view->txt[eScr]->print_text(script_file.s); + } + } +} + +App::~App() { + if (output_port==eJack) { + delete jack_interface; delete wfile_play; + } +} + +bool App::read_script(const char *script) { + static char textbuffer[2000]; + FILE *in=0; + if (!(in=fopen(script,"r"))) { + alert("'%s' unknown",script); + return false; + } + EditScript *es=app_local->editScript; + es->textview->read_file(in); + fclose(in); + es->reset_sbar(); + const char *text=es->textview->get_text(textbuffer,2000); + if (run_script(text)) { + MusicView *mv=app_local->musicView; + mv->set_scroll_range(); mv->draw_sc2(false); mv->exec_info_t0(); + return true; + } + return false; +} + +bool App::save_script(const char *s) { + FILE *out=0; + if (!(out=fopen(s,"w"))) { + alert("'%s' not writable",s); + return false; + } + app_local->editScript->textview->write_file(out); + fclose(out); + return true; +} + +void App::modify_script(EditScript *editS,int start,int end) { + char old_text[2000]; + int nr; + Str str; + bool ok=true; + RewrScript rwscr(start,end,act_meter); + editS->textview->get_text(old_text,2000); + editS->textview->reset(); + char *s,*p,*ib; + for (nr=-1,s=old_text;*s;s=p+1) { + for (p=s,ib=rwscr.in_buf;;++p) { + if (!*p) { *(ib++)='\n'; break; } + *(ib++)=*p; + if (*p=='\n') break; + if (ib-rwscr.in_buf>=max200) { + alert("modify_script: line > %d chars",max200); + return; + } + } + *ib=0; + rwscr.rewr_line(ok); + if (debug) printf("inl:[%s] outl:[%s]\n",rwscr.in_buf,rwscr.out_buf); + if (!ok) break; + editS->textview->set_line(rwscr.out_buf,++nr); + if (!*p) break; // last line does not end with '\n' + } +} + +int RewrScript::read_rwtime(Str &str,int &pos) { + str.strtok(in_buf," .,\n;#",pos); + int nr=atoi(str.s) * meter; + if (str.ch=='.') { + str.strtok(in_buf," ,\n;#",pos); + nr+=atoi(str.s); + } + return nr; +} + +void RewrScript::rewr_params(Str &str,int &pos,int mode,char *&ob,bool &omit) { + int tim; + char *lst_ob,*prev_ob; + omit=false; + for (;;) { + str.strtok(in_buf," :;\n#",pos); + if (str.ch==':') { + if (str=="time") { + str_cpy(ob,"time:"); + lst_ob=prev_ob=ob; + for(;;) { + tim=read_rwtime(str,pos); + if (insert_gap) { + if (tim>=start) tim+=stop-start+1; + } + else { + if (debug) printf("tim=%d start=%d stop=%d\n",tim,start,stop); + switch (mode) { + case eAdd_Take: + if (tim>=start) tim+=stop-start+1; + else if (tim>stop) { + ob=lst_ob; + goto find_comma; + } + break; + case eSet: + if (tim>=start) tim+=stop-start+1; + else if (tim>stop) tim=stop+1; + break; + } + } + if (tim%meter > 0) + ob += sprintf(ob,"%d.%d",tim/meter,tim%meter); + else + ob += sprintf(ob,"%d",tim/meter); + find_comma: + if (str.ch!=',') break; + lst_ob=ob; + str_cpy(ob,","); + } + if (mode==eAdd_Take && ob==prev_ob) omit=true; + } + else { + str_cpy(ob,str.s); + str_cpy(ob,":"); + str.strtok(in_buf," ;\n#",pos); + str_cpy(ob,str.s); + } + } + else + str_cpy(ob,str.s); + if (str.ch=='\n' || str.ch=='#') break; + char_cpy(str.ch,ob); + } +} + +void RewrScript::rewr_line(bool &ok) { + int pos=0; + char *ob=out_buf, + *prev_ob; + bool omit; + Str str; + for (;;) { + prev_ob=ob; + str.strtok(in_buf," :;\n#",pos); + + if (str.ch=='#') { + str_cpy(ob,"#"); + str_cpy(ob,in_buf+pos); + --ob; // strip '\n' + str.strtok(in_buf,"\n",pos); + break; + } + if (!str.s[0]); + else if (str=="put" || str=="in-par" || str=="out-par") { + str_cpy(ob,str.s); str_cpy(ob," "); + str.strtok(in_buf,";\n",pos); + str_cpy(ob,str.s); + } + else if (str=="exit" || str=="extended-syntax") { + str_cpy(ob,str.s); + } + else if (str=="set") { + str_cpy(ob,"set "); + rewr_params(str,pos,eSet,ob,omit); + } + else if (str=="add" || + str=="take" || + str=="take-nc") { + str_cpy(ob,str.s); str_cpy(ob," "); + rewr_params(str,pos,eAdd_Take,ob,omit); + if (omit) ob=prev_ob; + } + else { + alert("modify script: unknown cmd '%s'",str.s); + ok=false; + return; + } + if (str.ch=='\n') break; + if (str.ch=='#') { + str_cpy(ob," #"); + str_cpy(ob,in_buf+pos); + --ob; // strip '\n' + str.strtok(in_buf,"\n",pos); + break; + } + char_cpy(str.ch,ob); + } + *ob=0; +} + +bool App::restore(const char *file,bool add_tunes) { + bool result=true; + Encode enc; + Score* scp=0; + FILE *in=0; + selected.reset(); + if (!add_tunes) { + scores.reset(); + mn_buf.reset(0); + tnames.reset(); + if (app_local) { the_sv(0)->reset(); the_sv(1)->reset(); } + } + if ((in=fopen(file,"r"))==0) { + alert("file '%s' not found",file); + return false; + } + for (;;) { + enc.save_buf.rword(in," \n"); + if (!enc.save_buf.s[0]) break; + if (isdigit(enc.save_buf.s[0]) && isalpha(enc.save_buf.s[1])) { + if (!enc.decode(in,scp,!add_tunes)) { result=false; break; } + } + else { + alert("bad .sco code: %s",enc.save_buf.s); + result=false; + break; + } + } + if (app_local) // 0 at startup, meterView->draw() in constructor + app_local->meterView->draw(act_meter); + fclose(in); + return result; +} + +void get_token(Str& str,const char *text,const char* delim,int& pos) { + //if (debug) printf("get_token: str=[%s] delim=[%s] pos=%d\n",str.s,delim,pos); + str.strtok(text,delim,pos); +} + +bool read_times(const char *text,Str& str,int& pos,Array& tim,const int stop) { // e.g. time:2.3,4 + int nr; + for (int n=0;;++n) { + get_token(str,text," .\n;,",pos); + nr=atoi(str.s) * app->act_meter; + if (str.ch=='.') { + get_token(str,text," \n;,",pos); + nr+=atoi(str.s); + } + if (nr<0) { + alert("negative time"); return false; + } + tim[n]=nr; + if (str.ch!=',') { tim[n+1]=stop; return true; } + } +} + +int read_time(Str& str,const char *text,int& pos,int meter) { // e.g. time:2.3 + get_token(str,text," .\n;",pos); + bool neg= str.s[0]=='-'; + int nr=atoi(str.s) * meter; + if (str.ch=='.') { + get_token(str,text," \n;",pos); + if (neg) nr-=atoi(str.s); else nr+=atoi(str.s); + } + return nr; +} + +void set_wave(Str &str,const char *text,int &pos,const char* col, + int en,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + get_token(str,text," ,\n;",pos); + if (str.ch!=',') { alert("bad %s syntax",col); return; } + n=atoi(str.s); + if (n<1 || n>5) { alert("bad %s value: %d",col,n); n=2; } + info.idata.d0=n; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (n<2 || n>5) { alert("bad %s value: %d",col,n); n=3; } + info.idata.d1=n; + info.tag=en; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_mode(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.tag=eMode; + info.idata.col=col; + get_token(str,text," \n;",pos); + info.idata.d1=str.s[0]; // 'n': native, 'm': mono-synth + if (!strchr("inm",info.idata.d1)) { alert("bad %s: parameter (must be 'nat' or 'msynth')",colname); return; } + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_ph_model(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.tag=ePhysM; + info.idata.col=col; + get_token(str,text," ,\n;",pos); + if (str.ch!=',') { alert("bad %s syntax, missing ','",colname); return; } + n=atoi(str.s); + if (n<1 || n>5) { alert("bad %s value: %d",colname,n); n=2; } + info.idata.d0=n; + get_token(str,text," ,\n;",pos); + n=atoi(str.s); + if (n<1 || n>5) { alert("bad %s value: %d",colname,n); n=3; } + info.idata.d1=n; + + get_token(str,text," ,\n;",pos); + n=atoi(str.s); + if (n<1 || n>5) { alert("bad %s value: %d",colname,n); n=2; } + info.idata.d2=n; + + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (!isdigit(str.s[0])) { alert("missing digit for %s",colname); n=0; } + else if (n>1) { alert("bad %s value: %d (expected: 0 or 1)",colname,n); n=0; } + info.idata.d3=n; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_mono_synth(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + ScInfo info; + info.tag=eSynth; + get_token(str,text," \n;",pos); + if (info.ms_data.fill_msynth_data(col,str.s)) + for (int n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_wavefile(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.idata.col=col; + info.tag=eWaveF; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (!isdigit(str.s[0])) { alert("missing digit for %s",colname); n=0; } + info.idata.d1=n; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_info(int low,int hi,Str &str,const char *text,int &pos,const char* colname, + int en,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (nhi) { alert("bad %s value: %d",colname,n); n=0; } + info.tag=en; + info.n=n; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_reverb(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + uint n; + ScInfo info; + info.tag=eRevb; + info.idata.col=col; + get_token(str,text," ,\n;",pos); + info.idata.d1=min(4,atoi(str.s)); + if (str.ch==',') { + get_token(str,text," \n;",pos); + info.idata.d2=min(1,atoi(str.s)); + } + else info.idata.d2=0; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_info2(int low,int hi,Str &str,const char *text,int &pos,const char* colname, + int en,int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (nhi) { alert("bad %s value: %d",colname,n); n=0; } + info.tag=en; + info.idata.col=col; + info.idata.d1=n; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_ampl(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + uint n; + ScInfo info; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (n>ampl_max) { alert("bad %s value: %d",colname,n); n=0; } + info.tag=eAmpl; + info.idata.col=col; + info.idata.d1=n; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_ampl_gr(Str &str,const char *text,int &pos,const char* colname, + int col,int gr_nr,Arraytimes,MusicView *musV) { + uint n; + ScInfo info; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (n>ampl_max) { alert("bad %s value: %d",colname,n); n=0; } + info.tag=eAmpl_gr; + info.idata.col=col; + info.idata.d1=n; + info.idata.d2=gr_nr; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_fm(Str &str,const char *text,int &pos,const char* col,int en, + Arraytimes,MusicView *musV) { + int n; + ScInfo info; + get_token(str,text," ,\n;",pos); + if (str.ch!=',') { alert("%s: no comma",col); return; } + n=atoi(str.s); + if (n<-1 || n>7) { alert("bad %s value: %d",col,n); n=0; } + info.idata.d0=n; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (n<0 || n>7) { alert("bad %s value: %d",col,n); n=0; } + info.idata.d1=n; + info.tag=en; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_mmod(Str &str,const char *text,int &pos,const char* col,int en, + Arraytimes,MusicView *musV) { + uint n; + ScInfo info; + get_token(str,text," ,\n;",pos); + if (str.ch!=',') { alert("%s: no comma",col); return; } + n=atoi(str.s); + if (n>5) { alert("bad %s value: %d",col,n); n=0; } + info.idata.d0=n; + get_token(str,text," \n;",pos); + n=atoi(str.s); + if (n>3) { alert("bad %s value: %d",col,n); n=0; } + info.idata.d1=n; + info.tag=en; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_bool(Str &str,const char *text,int &pos,const char* col, + int en,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + get_token(str,text," \n;",pos); + if (str=="on") info.b=true; + else if (str=="off") info.b=false; + else { alert("bad %s value: %s",col,str.s); info.b=false; } + info.tag=en; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_bool2(Str &str,const char *text,int &pos,const char* colname, + int en,int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + get_token(str,text," \n;",pos); + if (str=="on") info.idata.b=true; + else if (str=="off") info.idata.b=false; + else { alert("bad %s value: %s",col,str.s); info.b=false; } + info.tag=en; + info.idata.col=col; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_loc(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.idata.col=col; + info.tag=eLoc; + get_token(str,text," \n;",pos); + if (str=="left") info.idata.d1=eLeft; + else if (str=="right") info.idata.d1=eRight; + else if (str=="mid") info.idata.d1=eMid; + else { alert("loc: bad value: %s",str.s); info.idata.d1=eMid; } + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_pan(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n, val=eMid; + ScInfo info; + info.idata.col=col; + info.tag=eLoc; + get_token(str,text," \n;",pos); + if (str=="LL") val=eLLeft; + else if (str=="L") val=eLeft; + else if (str=="M") val=eMid; + else if (str=="R") val=eRight; + else if (str=="RR") val=eRRight; + else { alert("pan: bad value: %s",str.s); } + info.idata.d1=val; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_sampled_loc(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.idata.col=col; + info.tag=eSLoc; + get_token(str,text," \n;",pos); + if (str=="left") info.idata.d1=eLeft; + else if (str=="right") info.idata.d1=eRight; + else if (str=="mid") info.idata.d1=eMid; + else { alert("sampled-loc: bad value: %s",str.s); info.n=0; } + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_sampled_pan(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n, val=eMid; + ScInfo info; + info.idata.col=col; + info.tag=eSLoc; + get_token(str,text," \n;",pos); + if (str=="LL") val=eLLeft; + else if (str=="L") val=eLeft; + else if (str=="M") val=eMid; + else if (str=="R") val=eRight; + else if (str=="RR") val=eRRight; + else { alert("sampled-pan: bad value: %s",str.s); } + info.idata.d1=val; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_is_a(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.idata.col=col; + get_token(str,text," \n;",pos); + info.idata.d1=color_nr(str.s); + info.tag=eIsa; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_ms_is_a(Str &str,const char *text,int &pos,const char* colname, + int col,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.idata.col=col; + get_token(str,text," \n;",pos); + info.idata.d1=color_nr(str.s); + info.tag=eMsIsa; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +void set_isa_gr(Str &str,const char *text,int &pos,const char* colname, + int col,int gr_nr,Arraytimes,MusicView *musV) { + int n; + ScInfo info; + info.idata.col=col; + get_token(str,text," \n;",pos); + info.idata.d1=color_nr(str.s); + info.idata.d2=gr_nr; + info.tag=eIsa_gr; + for (n=0;times[n]!=eo_arr;++n) musV->upd_info(times[n],info); +} + +bool key_name_to_keynr(char *kn,int& key_nr) { + int n; + for (n=0;;++n) { + if (!strcmp(kn,maj_min_keys[n])) break; + if (n==keys_max*4-1) { alert("unknown key %s",kn); key_nr=0; return false; } + } + key_nr=n%(2*keys_max); // n > 2*keys_max: classic key name + return true; +} + +bool App::run_script(const char *text) { + Score *from=0, + *to=0; + Str str; + bool ok; + int n,n1, + cmd, + pos,thePos, + line_nr, + lst_ch, + col_nr=eBlack; + char *s; + ScInfo info; + Array times; + ScoreViewBase *display=0; + ScoreView *sv; + MusicView* musV=app_local->musicView; + + app_local->editScript->meas_info->reset(); + for (n=0;nset_isa(n); + ps_out.init_ps(); + extended=false; + str.cmt_ch='#'; + + for (pos=thePos=0,line_nr=1;;) { + lst_ch=str.ch; + get_token(str,text," :\n;",pos); + if (!str.s[0] && str.ch==0) break; + if (!str.s[0]) { ++line_nr; continue; } + if (debug) printf("cmd:%s\n",str.s); + if ((cmd=eAdd,str=="add") || // from scores to music + (cmd=eTake,str=="take") || // from scores to scoreBuf + (cmd=eTake_nc,str=="take-nc")) { // from scores to scoreBuf, no clear + switch (cmd) { + case eAdd: + thePos=pos; + from=scoreBuf; to=musV->score; display=musV; + break; + case eTake: + from=0; to=scoreBuf; display=0; + to->reset(false); + break; + case eTake_nc: + from=0; to=scoreBuf; display=0; + break; + } + times[0]=to->lst_sect+1; + times[1]=eo_arr; + int start=0, + stop=nop, + shift=0, + raise=0, + atcolor=-1; + for (;;) { + if (strchr("\n;",str.ch)) { + if (!from) { alert("error in script"); return false; } + int act_stop = stop>0 ? stop : from->end_sect ? from->end_sect : from->len; + switch (cmd) { + case eAdd: break; + case eTake: // to = scoreBuf + to->name=from->name; // needed for command "add" + to->signs_mode=from->signs_mode; + to->sc_meter=from->sc_meter; + for (n=0;nlin[n].n_sign=from->lin[n].n_sign; + break; + case eTake_nc: break; + } + for (n=0;times[n]!=eo_arr;++n) + to->add_copy(from,start,act_stop,times[n],shift,raise,display,atcolor); + if (cmd==eAdd && lst_ch=='\n') + app_local->editScript->print_meas(line_nr-1,times[0]); + break; + } + get_token(str,text," :\n;",pos); + if (debug) printf("add/take cmd: %s ch=[%c]\n",str.s,str.ch); + if (str.ch==':') { + if (str=="time") { + if (!read_times(text,str,pos,times,eo_arr)) return false; + } + else if (str=="rt") { + times[0]=to->lst_sect+1+read_time(str,text,pos,act_meter); + if (times[0]<0) { alert("negative time at rt: command"); return false; } + times[1]=eo_arr; + } + else if (str=="from") { + int m=from->sc_meter; + if (!m) m=act_meter; + start=read_time(str,text,pos,m); + if (start<0) { alert("negative time at from: command"); return false; } + } + else if (str=="to") { + int m=from->sc_meter; + if (!m) m=act_meter; + stop=read_time(str,text,pos,m); + if (stop<0) { alert("negative time at stop: command"); return false; } + } + else if (str=="shift") { get_token(str,text," \n;",pos); shift=atoi(str.s); } + else if (str=="raise") { get_token(str,text," \n;",pos); raise=atoi(str.s); } + else if (str=="color") { + get_token(str,text," \n;",pos); + atcolor=color_nr(str.s); + } + else { alert("unk option '%s'",str.s); return false; } + } + else { + from=scores.exist_name(str.s); + if (!from) { + if (str==title()) + from=the_mv()->score; + else { + alert("tune '%s' unknown, line %d",str.s,line_nr); return false; + } + } + } + } + } + else if (str=="put") { // from scoreBuf to scores + if (str.ch!=' ') { alert("tune name missing after put cmd"); return false; } + get_token(str,text," \n;",pos); + to=scores.exist_name(str.s); + if (to) { + to->reset(false); + to->copy(scoreBuf); + if (find_score(to,sv)) { sv->set_scroll_range(); sv->draw_sc2(true); } + } + else { + to=new_score(str.s); + to->copy(scoreBuf); + } + } + else if (str=="out-par") { + int group; + times[0]=0; + times[1]=eo_arr; + for(;;) { + get_token(str,text," :\n;",pos); + if (debug) printf("out-par cmd:%s\n",str.s); + if (str.ch==':') { + group=0; + if (str=="time") { + if (!read_times(text,str,pos,times,eo_arr)) return false; + } + else if (!strncmp(str.s,"transp-",7)) { // e.g. "transp-0" + group=atoi(str.s+7); + if (group<0 || group>=groupnr_max) { alert("transp: group = %d",group); return false; } + for (n=0;n24) { alert("bad transpose value: %d",n1); n1=0; } + ps_out.transp[group][n]=n1; + } + } + else if (str=="annotate") { + for (n=0;;++n) { + if (n>=annot_max) { + alert("out-par: annotation > 'Z', line %d",line_nr); break; + } + get_token(str,text," \n;,",pos); + ps_out.annot[n].mnr=atoi(str.s); + if (str.ch!=',') break; + } + } + else if (str=="key") { + get_token(str,text," \n;",pos); + if (!key_name_to_keynr(str.s,app->mv_key_nr)) return false; + } + else if (str=="nupq") { // note units per quarter note + get_token(str,text," \n;",pos); + app->nupq=atoi(str.s); + } + else if (!strncmp(str.s,"midi-instr-",11)) { // e.g. "midi-instr-0" + group=atoi(str.s+11); + if (group<0 || group>=groupnr_max) { alert("midi-instr: group = %d",group); return false; } + info.tag=eMidiInstr; + for (n=0;n128) { + alert("bad midi-instr value: %d",n1); n1=0; + } + info.midi_data.n6[n]=n1; + info.midi_data.gnr=group; + } + for (n=0;times[n]!=eo_arr;++n) + musV->upd_info(times[n],info); + } + else if (!strncmp(str.s,"midi-perc-",10)) { + info.tag=eMidiPerc; + group=atoi(str.s+10); + if (group<0 || group>=groupnr_max) { alert("midi-perc: group = %d",group); return false; } + for (n=0;n81)) { // not mapped: n1=0 + alert("bad midi-perc value: %d",n1); n1=35; + } + info.midi_data.n6[n]=n1; + info.midi_data.gnr=group; + } + for (n=0;times[n]!=eo_arr;++n) + musV->upd_info(times[n],info); + } + else if (str=="header" || str=="abc-header") { + get_token(str,text," \n;",pos); + ps_out.header_file=strdup(str.s); + } + else { + alert("unk option '%s:' with out-par cmd, line %d",str.s,line_nr); + return false; + } + } + else if (str=="single-voice") { + ps_out.s_voice=true; + } + else { + alert("unk option '%s' at out-par cmd, line %d",str.s,line_nr); + return false; + } + if (strchr("\n;",str.ch)) break; + } + } + else if (str=="sample-dir" && str.ch==':') { // samples directories + wave_buf.sample_dirs->reset(); + for (;;) { + XftColor *dircol; // fontcolor in wavefile menu + const char *cs; + get_token(str,text," ,\n;",pos); + if (str==cur_dir) { cs=cur_dir; dircol=xft_Black; } + else if (str==wave_samples) { cs=wave_samples; dircol=xft_Blue; } + else { + cs=strdup(str.s); + static XftColor *dc=xft_calc_color(0,0x70,0); dircol=dc; + } + wave_buf.sample_dirs->add_dir(cs,dircol); + if (str.ch!=',') break; + } + } + else if (str=="set") { + times[0]=musV->score->lst_sect+1; // default time + times[1]=eo_arr; + for (;;) { + get_token(str,text," :\n;",pos); + if (debug) printf("set cmd:%s\n",str.s); + if (str.ch==':') { + if (str=="time") { + if (!read_times(text,str,pos,times,eo_arr)) return false; + } + else if (str=="rt") { + times[0]=musV->score->lst_sect+1+read_time(str,text,pos,act_meter); + if (times[0]<0) { alert("negative time at rt: command"); return false; } + times[1]=eo_arr; + } + else if (str=="tempo") { + get_token(str,text," \n;",pos); + info.tag=eTempo; + info.n=atoi(str.s)/10; + for (n=0;times[n]!=eo_arr;++n) + musV->upd_info(times[n],info); + } + else if (eq(str.s,col_nr,"red","start-wave","stw",s)) + set_wave(str,text,pos,s,eRed_att_timbre,times,musV); + else if (eq(str.s,col_nr,"red","sustain-wave","suw",s)) + set_wave(str,text,pos,s,eRed_timbre,times,musV); + else if (eq(str.s,col_nr,"red","decay","dc",s)) + set_info(0,5,str,text,pos,s,eRed_decay,times,musV); + else if (eq(str.s,col_nr,"red","startup","su",s)) + set_info(0,5,str,text,pos,s,eRed_attack,times,musV); + else if (eq(str.s,col_nr,"red","start-amp","sa",s)) + set_info(0,3,str,text,pos,s,eRed_stamp,times,musV); + else if (eq(str.s,col_nr,"green","freq-ratio","fr",s)) + set_info(0,3,str,text,pos,s,eGreen_fmul,times,musV); + else if (eq(str.s,col_nr,"green","chorus","ch",s)) + set_info(0,3,str,text,pos,s,eGreen_chorus,times,musV); + else if (eq(str.s,col_nr,"green","attack","at",s)) + set_info(0,5,str,text,pos,s,eGreen_attack,times,musV); + else if (eq(str.s,col_nr,"green","decay","dc",s)) + set_info(0,5,str,text,pos,s,eGreen_decay,times,musV); + else if (eq(str.s,col_nr,"green","tone",0,s)) { + get_token(str,text," \n;",pos); + alert("skipping green 'tone' command"); + } + else if (eq(str.s,col_nr,"green","wave1","w1",s)) + set_wave(str,text,pos,s,eGreen_timbre1,times,musV); + else if (eq(str.s,col_nr,"green","wave2","w2",s)) + set_wave(str,text,pos,s,eGreen_timbre2,times,musV); + else if (eq(str.s,col_nr,"purple","decay","dc",s)) + set_info(0,5,str,text,pos,s,ePurple_decay,times,musV); + else if (eq(str.s,col_nr,"purple","sound","snd",s)) + set_info(0,4,str,text,pos,s,ePurple_tone,times,musV); + else if (eq(str.s,col_nr,"purple","tone",0,s)) { + alert("'purple tone:' is deprecated, use 'sound:'"); + get_token(str,text," \n;",pos); + } + else if (eq(str.s,col_nr,"blue","attack","at",s)) + set_info(0,5,str,text,pos,s,eBlue_attack,times,musV); + else if (eq(str.s,col_nr,"blue","decay","dc",s)) + set_info(0,5,str,text,pos,s,eBlue_decay,times,musV); + else if (eq(str.s,col_nr,"blue","dur-limit","dl",s)) + set_info(0,4,str,text,pos,s,eBlue_durlim,times,musV); + else if (eq(str.s,col_nr,"blue","lowpass","lp",s)) + set_info(0,4,str,text,pos,s,eBlue_lowp,times,musV); + else if (eq(str.s,col_nr,"red","dur-limit","dl",s)) + set_info(0,4,str,text,pos,s,eRed_durlim,times,musV); + else if (eq(str.s,col_nr,"red","tone",0,s)) + set_info(0,1,str,text,pos,s,eRed_tone,times,musV); + else if (eq(str.s,col_nr,"blue","rich","rich",s)) + set_bool(str,text,pos,s,eBlue_rich,times,musV); + else if (eq(str.s,col_nr,"blue","chorus","ch",s)) + set_bool(str,text,pos,s,eBlue_chorus,times,musV); + else if ((eq(str.s,col_nr,"black","fm",0,s))) + set_fm(str,text,pos,s,eBlack_fm,times,musV); + else if (eq(str.s,col_nr,"black","decay","dc",s)) + set_info(0,5,str,text,pos,s,eBlack_decay,times,musV); + else if (eq(str.s,col_nr,"black","attack","at",s)) + set_info(0,5,str,text,pos,s,eBlack_attack,times,musV); + else if (eq(str.s,col_nr,"black","subband","sb",s) || + eq(str.s,col_nr,"brown","subband","sb",s)) { + alert("'black/brown subband: (sb:)' deprecated, use e.g. fm:-1,.."); + get_token(str,text," \n;",pos); + } + else if (eq(str.s,col_nr,"black","detune","dt",s)) + set_info(0,5,str,text,pos,s,eBlack_detune,times,musV); + else if ((eq(str.s,col_nr,"black","modmod","mm",s))) + set_mmod(str,text,pos,s,eBlack_mmod,times,musV); + else if (eq(str.s,col_nr,"brown","fm",0,s)) + set_fm(str,text,pos,s,eBrown_fm,times,musV); + else if (eq(str.s,col_nr,"brown","decay","dc",s)) + set_info(0,5,str,text,pos,s,eBrown_decay,times,musV); + else if (eq(str.s,col_nr,"brown","detune","dt",s)) + set_info(0,5,str,text,pos,s,eBrown_detune,times,musV); + else if (eq(str.s,col_nr,"brown","attack","at",s)) + set_info(0,5,str,text,pos,s,eBrown_attack,times,musV); + else if ((eq(str.s,col_nr,"brown","modmod","mm",s))) + set_mmod(str,text,pos,s,eBrown_mmod,times,musV); + else if (eq(str.s,col_nr,"purple","start-harm","sth",s)) { + info.tag=ePurple_a_harm; + for (n=0;n3) { alert("bad %s value: %d",s,n1); n1=0; } + info.n5[n]=n1; + } + for (n=0;times[n]!=eo_arr;++n) + musV->upd_info(times[n],info); + } + else if (eq(str.s,col_nr,"purple","sustain-harm","suh",s)) { + info.tag=ePurple_s_harm; + for (n=0;n3) { alert("bad %s value: %d",s,n1); n1=0; } + info.n5[n]=n1; + } + for (n=0;times[n]!=eo_arr;++n) + musV->upd_info(times[n],info); + } + else if (eq(str.s,col_nr,"purple","startup","su",s)) + set_info(0,5,str,text,pos,s,ePurple_attack,times,musV); + else if (eq(str.s,col_nr,"purple","chorus","ch",s)) { + alert("'purple chorus: (ch:)' is deprecated, use 'sound:1'"); + get_token(str,text," \n;",pos); + } + else if (eq(str.s,col_nr,"black","sampled",0,s) || + eq(str.s,col_nr,"red","sampled",0,s) || + eq(str.s,col_nr,"green","sampled",0,s) || + eq(str.s,col_nr,"blue","sampled",0,s) || + eq(str.s,col_nr,"brown","sampled",0,s) || + eq(str.s,col_nr,"purple","sampled",0,s)) { + get_token(str,text," \n;",pos); + alert("'sampled:' is deprecated, use 'phm:' and 'wa:'"); + } + else if (eq(str.s,col_nr,"black","msynth",0,s) || + eq(str.s,col_nr,"brown","msynth",0,s) || + eq(str.s,col_nr,"blue","msynth",0,s) || + eq(str.s,col_nr,"green","msynth",0,s) || + eq(str.s,col_nr,"red","msynth",0,s) || + eq(str.s,col_nr,"purple","msynth",0,s)) + set_mono_synth(str,text,pos,s,col_nr,times,musV); + else if (eq(str.s,col_nr,"black","mode","m",s) || + eq(str.s,col_nr,"blue","mode","m",s) || + eq(str.s,col_nr,"red","mode","m",s) || + eq(str.s,col_nr,"green","mode","m",s) || + eq(str.s,col_nr,"purple","mode","m",s) || + eq(str.s,col_nr,"brown","mode","m",s)) + set_mode(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","wavefile","wf",s) || + eq(str.s,col_nr,"red","wavefile","wf",s) || + eq(str.s,col_nr,"green","wavefile","wf",s) || + eq(str.s,col_nr,"blue","wavefile","wf",s) || + eq(str.s,col_nr,"brown","wavefile","wf",s) || + eq(str.s,col_nr,"purple","wavefile","wf",s)) + set_wavefile(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","phys-mod","phm",s) || + eq(str.s,col_nr,"red","phy-mod","phm",s) || + eq(str.s,col_nr,"green","phys-mod","phm",s) || + eq(str.s,col_nr,"blue","phys-mod","phm",s) || + eq(str.s,col_nr,"brown","phys-mod","phm",s) || + eq(str.s,col_nr,"purple","phys-mod","phm",s)) + set_ph_model(str,text,pos,s,col_nr,times,musV); + else if (eq(str.s,col_nr,"black","reverb","rev",s) || + eq(str.s,col_nr,"red","reverb","rev",s) || + eq(str.s,col_nr,"blue","reverb","rev",s) || + eq(str.s,col_nr,"green","reverb","rev",s) || + eq(str.s,col_nr,"purple","reverb","rev",s) || + eq(str.s,col_nr,"brown","reverb","rev",s)) + set_reverb(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","wave-ampl","wa",s) || + eq(str.s,col_nr,"red","wave-ampl","wa",s) || + eq(str.s,col_nr,"green","wave-ampl","wa",s) || + eq(str.s,col_nr,"blue","wave-ampl","wa",s) || + eq(str.s,col_nr,"brown","wave-ampl","wa",s) || + eq(str.s,col_nr,"purple","wave-ampl","wa",s)) + set_info2(0,7,str,text,pos,s,eWaveAmpl,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","ctr-pitch","cpit",s) || + eq(str.s,col_nr,"red","ctr-pitch","cpit",s) || + eq(str.s,col_nr,"green","ctr-pitch","cpit",s) || + eq(str.s,col_nr,"blue","ctr-pitch","cpit",s) || + eq(str.s,col_nr,"brown","ctr-pitch","cpit",s) || + eq(str.s,col_nr,"purple","ctr-pitch","cpit",s)) + set_bool2(str,text,pos,s,eCtrPitch,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","base_freq","bf",s) || + eq(str.s,col_nr,"brown","base_freq","bf",s)) + set_bool2(str,text,pos,s,eBaseFreq,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","a-0","a-1","a-2",n,s) || + eq(str.s,col_nr,"red","a-0","a-1","a-2",n,s) || + eq(str.s,col_nr,"green","a-0","a-1","a-2",n,s) || + eq(str.s,col_nr,"blue","a-0","a-1","a-2",n,s) || + eq(str.s,col_nr,"purple","a-0","a-1","a-2",n,s) || + eq(str.s,col_nr,"brown","a-0","a-1","a-2",n,s)) + if (extended) + set_ampl_gr(str,text,pos,s,col_nr,n,times,musV); + else { alert("%s: only if extended-syntax",str.s); return false; } + + else if (eq(str.s,col_nr,"black","ampl","a",s) || + eq(str.s,col_nr,"brown","ampl","a",s) || + eq(str.s,col_nr,"red","ampl","a",s) || + eq(str.s,col_nr,"green","ampl","a",s) || + eq(str.s,col_nr,"blue","ampl","a",s) || + eq(str.s,col_nr,"purple","ampl","a",s)) + set_ampl(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","loc",0,s) || + eq(str.s,col_nr,"red","loc",0,s) || + eq(str.s,col_nr,"green","loc",0,s) || + eq(str.s,col_nr,"blue","loc",0,s) || + eq(str.s,col_nr,"brown","loc",0,s) || + eq(str.s,col_nr,"purple","loc",0,s)) + set_loc(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","pan",0,s) || + eq(str.s,col_nr,"red","pan",0,s) || + eq(str.s,col_nr,"green","pan",0,s) || + eq(str.s,col_nr,"blue","pan",0,s) || + eq(str.s,col_nr,"brown","pan",0,s) || + eq(str.s,col_nr,"purple","pan",0,s)) + set_pan(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","sampled-loc","sloc",s) || + eq(str.s,col_nr,"red","sampled-loc","sloc",s) || + eq(str.s,col_nr,"green","sampled-loc","sloc",s) || + eq(str.s,col_nr,"blue","sampled-loc","sloc",s) || + eq(str.s,col_nr,"brown","sampled-loc","sloc",s) || + eq(str.s,col_nr,"purple","sampled-loc","sloc",s)) + set_sampled_loc(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","sampled-loc","span",s) || + eq(str.s,col_nr,"red","sampled-loc","span",s) || + eq(str.s,col_nr,"green","sampled-loc","span",s) || + eq(str.s,col_nr,"blue","sampled-loc","span",s) || + eq(str.s,col_nr,"brown","sampled-loc","span",s) || + eq(str.s,col_nr,"purple","sampled-loc","span",s)) + set_sampled_pan(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","eq",0,s) || + eq(str.s,col_nr,"red","eq",0,s) || + eq(str.s,col_nr,"green","eq",0,s) || + eq(str.s,col_nr,"blue","eq",0,s) || + eq(str.s,col_nr,"brown","eq",0,s) || + eq(str.s,col_nr,"purple","eq",0,s)) + set_is_a(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","ms-eq",0,s) || + eq(str.s,col_nr,"red","ms-eq",0,s) || + eq(str.s,col_nr,"green","ms-eq",0,s) || + eq(str.s,col_nr,"blue","ms-eq",0,s) || + eq(str.s,col_nr,"brown","ms-eq",0,s) || + eq(str.s,col_nr,"purple","ms-eq",0,s)) + set_ms_is_a(str,text,pos,s,col_nr,times,musV); + + else if (eq(str.s,col_nr,"black","eq-0","eq-1","eq-2",n,s) || + eq(str.s,col_nr,"red","eq-0","eq-1","eq-2",n,s) || + eq(str.s,col_nr,"blue","eq-0","eq-1","eq-2",n,s) || + eq(str.s,col_nr,"green","eq-0","eq-1","eq-2",n,s) || + eq(str.s,col_nr,"purple","eq-0","eq-1","eq-2",n,s) || + eq(str.s,col_nr,"brown","eq-0","eq-1","eq-2",n,s)) { + if (extended) + set_isa_gr(str,text,pos,s,col_nr,n,times,musV); + else { alert("%s: only if extended-syntax",str.s); return false; } + } + else { + alert("bad option '%s' at set cmd, line %d",str.s,line_nr); + return false; + } + if (strchr("\n;",str.ch)) break; + } + else if (col_nr=color_nr(str.s,&ok),ok); // here col_nr is set + else { + alert("unk option '%s' at set cmd, line %d",str.s,line_nr); + return false; + } + } + } + else if (str=="mute") { + alert("'mute' not supported, use 'ampl:0'"); + return false; + } + else if (str=="exit") break; + else if (str=="extended-syntax") { + if (!extended) { + extended=true; + for (int col=0;colgroup=new RButWin(ctr->cview,ctr->gr_rect,FN,"gr#",false,rbutwin_cmd,cForeground,Id('fmgr',col)); + for (int i=0;i<3;++i) ctr->group->add_rbut(i==0?"0":i==1?"1":"2"); + } + } + } + else { alert("unknown cmd '%s' in script, line %d",str.s,line_nr); return false; } + if (str.ch==0) break; + if (str.ch=='\n') ++line_nr; + } + return true; +} + +char* App::title() { + static Str tit; + tit.cpy(input_file.strip_dir()); + char *p=strrchr(tit.s,'.'); + if (p) p[0]=0; + return tit.s; +} + +void ChordsWindow::force_key(Id) { + if (app->act_score>nop) { + ScoreView *sv=the_sv(app->act_score); + if (sv->draw_mode==ePiano) return; + if (sv->score) { + for (int n1=0;n1score->lin[n1]; + for (int n2=0;n2score->len;++n2) { + ScSection *sec=lin->sect+n2; + if (sec->cat==ePlay) + for (;sec;sec=sec->nxt()) + sec->sign=lin->n_sign; + } + } + sv->draw_sc2(false); + } + app->check_reset_as(); + } +} + +void ChordsWindow::scr_cmd(Id,int val,int,bool) { + static ChordsWindow *cw=app->chordsWin; + cw->scales->set_y_off(val); +} + +void ChordsWindow::set_key(Id) { + if (app->act_score>nop) { + ScoreView *sv=the_sv(app->act_score); + ChordsWindow *cw=app->chordsWin; + if (sv->score) { + sv->score->tone2signs(cw->the_key_nr); + switch (sv->draw_mode) { + case eAccValue: + sv->draw_sc2(true); + break; + case ePiano: + sv->draw_sc2(true); + break; + default: + sv->drawSigns(); + } + } + app->check_reset_as(); + } +} + +void ScoreView::draw_chordn(bool store_name) { + if (store_name) + app->chordsWin->chord_or_scale_name(the_chord_name,score->sc_key_nr); + int loc=max(2,(chord_name->dx - xft_text_width(the_chord_name))/2); + chord_name->clear(); + xft_draw_string(chord_name->xft_win,xft_Black,Point(loc,10),the_chord_name); +} + +void ChordsWindow::upd_chordname(ScoreView *sv) { + sv->draw_chordn(true); + sv->display_mode->re_label(sv->display_mode->nr2rb(3),"chord, scale"); +} + +void ChordsWindow::upd_scoreview(int scales_nr) { + if (app->act_score>nop && scales->act_button) { + ScoreView *sv=the_sv(app->act_score); + sv->p_set_scline_col(scale_data[scales_nr].valid,scale_data[scales_nr].gt12,shift_12); + sv->scale_dep= dm==eScDep_no ? eScDep_no : dep_mode->value ? eScDep_lk : eScDep_sk; + upd_chordname(sv); + if (sv->draw_mode==ePiano) + sv->draw_sc2(true); + } +} + +void CtrlBase::draw(Id id) { + col2ctrl(id.id1)->draw_isa_col(); +} + +void CtrlBase::draw_isa_col() { + int gr=group ? group->act_rbutnr() : 0; + uint line_col= isa[gr]==this ? cForeground : col2color(isa[gr]->ctrl_col); + set_width_color(2,line_col); + cview->draw_line(Point(1,0),Point(1,ictrl_height)); +} + +CtrlBase::CtrlBase(Rect rect,int col): + cview(new BgrWin(top_win->win,rect,MR,draw,cForeground,1,Id(col,0))), + bgwin(cview->win), + ctrl_col(col), + instrm_val(eNativeMode), + sample_mode(ePhysmodMode), + gr_rect(0,0,0,0), + inst(col<0 ? 0 : col2instr(col)), + synth_top(700,20), + synth(0), + group(0) { + for (int gr=0;gr<3;++gr) isa[gr]=this; +} + +void CtrlBase::init_ampl() { + ampl->d=ampl_val; + for (int i=0;i<3;++i) ampl_val[i]=5; + set_text(ampl->text,"%.2f",ampl_mult[5]); +} + +FMCtrl::FMCtrl(Rect rect,int col): // col: eBlack or eBrown + CtrlBase(rect,col) { + fm_ctrl=new HVSlider(cview,Rect(4,TDIST,82,68),18,FN,Int2(-1,0),Int2(7,7), + "FM freq/index","-1","7","0","7",hvslider_cmd,cForeground,Id('fm ',col)); + fm_ctrl->value.set(2,3); + set_text(fm_ctrl->text_x,"3.0"); + set_text(fm_ctrl->text_y,"2.0"); + + mode=new RButWin(cview,Rect(4,96,44,2*TDIST),FN,"mode",false,rbutton_cmd,cForeground,Id('moms',col)); + mode->add_rbut("native"); mode->add_rbut("msynth"); + + button_style.param=1; + eq=new Button(bgwin,Rect(54,97,28,0),FN,"eq?",button_cmd,Id('eq',col)); + button_style=def_but_st; + + detune=new HSlider(cview,Rect(92,TDIST,60,0),FN,0,5,"detune","0","5",slider_cmd,cForeground,Id('fmdt',col)); + detune->value()=0; + + mod_mod=new HVSlider(cview,Rect(86,40+TDIST,70,50),20,FN,Int2(0,0),Int2(5,3), + "MM depth/freq","0","5","0","3",hvslider_cmd,cForeground,Id('momo',col)); + mod_mod->value.set(0,0); + set_text(mod_mod->text_x,"0"); + set_text(mod_mod->text_y,"0.5"); + + base_freq=new CheckBox(bgwin,Rect(86,108,0,0),FN,cForeground,"base freq",0); + + gr_rect.set(156,82,18,3*TDIST); + + attack=new HSlider(cview,Rect(184,TDIST,56,0),FN,0,5,"attack","0","5",slider_cmd,cForeground,Id('fmat',col)); + attack->value()=0; + + decay=new HSlider(cview,Rect(184,56,56,0),FN,0,5,"decay","0","5",slider_cmd,cForeground,Id('fmde',col)); + decay->value()=1; + + ampl=new HSlider(cview,Rect(178,98,64,0),FN,0,9,"amplitude","0","9",slider_cmd,cForeground,Id('iamp',col)); + init_ampl(); + + synth=new Synth(synth_top,col,false); +} + +RedCtrl::RedCtrl(Rect rect,int col): CtrlBase(rect,col) { + mode=new RButWin(cview,Rect(4,18,44,2*TDIST),FN,"mode",false,rbutton_cmd,cForeground,Id('moms',col)); + mode->add_rbut("native"); mode->add_rbut("msynth"); + + button_style.param=1; + eq=new Button(bgwin,Rect(4,60,28,0),FN,"eq?",button_cmd,Id('eq',col)); + button_style=def_but_st; + + start_timbre=new HVSlider(cview,Rect(50,TDIST,64,46),16,FN,Int2(1,2),Int2(5,4), + "diff/nrsin","1","5","2","4",hvslider_cmd,cForeground,Id('radn')); + start_timbre->value.set(4,3); + + timbre=new HVSlider(cview,Rect(112,TDIST,62,46),14,FN,Int2(1,2),Int2(5,4), + "diff/nrsin","1","5","2","4",hvslider_cmd,cForeground,Id('rsdn')); + timbre->value.set(3,3); + + start_amp=new VSlider(cview,Rect(174,TDIST,24,44),FN,0,3,"start-amp","0","3",slider_cmd,cForeground,Id('samp')); + start_amp->value=2; + + tone=new RButWin(cview,Rect(202,32,38,2*TDIST),FN,"tone",false,rbutwin_cmd,cForeground,Id('redt')); + tone->set_rbut(tone->add_rbut("clean"),false); + tone->add_rbut("bright"); + + gr_rect.set(164,80,18,3*TDIST); + + startup=new HSlider(cview,Rect(4,96,50,0),FN,0,5,"startup","0","5",slider_cmd,cForeground,Id('rest')); + startup->value()=2; + + decay=new HSlider(cview,Rect(56,96,50,0),FN,0,5,"decay","0","5",slider_cmd,cForeground,Id('rede')); + decay->value()=1; + + dur_limit=new HSlider(cview,Rect(108,96,54,0),FN,0,4,"dur limit","0","4",slider_cmd,cForeground,Id('durl',eRed)); + set_text(dur_limit->text,"no limit"); + + ampl=new HSlider(cview,Rect(184,96,60,0),FN,0,9,"amplitude","0","9",slider_cmd,cForeground,Id('iamp',col)); + init_ampl(); + + cview->display_cmd=draw; + + synth=new Synth(synth_top,col,false); +} + +void RedCtrl::draw(Id id) { + CtrlBase::draw(id); + RedCtrl *ctr=app->red_control; + BgrWin *cv=ctr->cview; + static XftText txt[2] = { + XftText(cv->win,cv->xft_win,"startup",Point(50,70)), + XftText(cv->win,cv->xft_win,"sustain",Point(112,70)) + }; + for (int i=0;i<2;++i) txt[i].draw(); +} + +GreenCtrl::GreenCtrl(Rect rect,int col): CtrlBase(rect,col) { + mode=new RButWin(cview,Rect(4,TDIST,44,2*TDIST),FN,"mode",false,rbutton_cmd,cForeground,Id('moms',col)); + mode->add_rbut("native"); + mode->add_rbut("msynth"); + + button_style.param=1; + eq=new Button(bgwin,Rect(4,50,28,0),FN,"eq?",button_cmd,Id('eq',col)); + button_style=def_but_st; + + timbre1=new HVSlider(cview,Rect(54,TDIST,50,44),12,FN,Int2(1,2),Int2(4,4), + "diff/nrsin","1","4","2","4",hvslider_cmd,cForeground,Id('grw1')); + timbre1->value.set(2,3); // var_sinus[1][1] + + timbre2=new HVSlider(cview,Rect(112,TDIST,50,44),12,FN,Int2(1,2),Int2(4,4), + "diff/nrsin","1","4","2","4",hvslider_cmd,cForeground,Id('grw2')); + timbre1->value.set(3,2); // var_sinus[2][0] + + freq_mult=new HSlider(cview,Rect(170,TDIST,54,0),FN,0,3,"freq ratio","0","3",slider_cmd,cForeground,Id('grfm')); + freq_mult->value()=2; + set_text(freq_mult->text,"2"); + + chorus=new HSlider(cview,Rect(170,52,54,0),FN,0,3,"chorus","0","3",slider_cmd,cForeground,Id('grfm')); // same cmd as freq_mult + chorus->value()=1; + + attack=new HSlider(cview,Rect(4,96,55,0),FN,0,5,"attack","0","5",slider_cmd,cForeground,Id('grat')); + attack->value()=1; + decay=new HSlider(cview,Rect(63,96,55,0),FN,0,5,"decay","0","5",slider_cmd,cForeground,Id('grde')); + decay->value()=3; + + gr_rect.set(138,82,18,3*TDIST); + + ampl=new HSlider(cview,Rect(170,96,66,0),FN,0,9,"amplitude","0","9",slider_cmd,cForeground,Id('iamp',col)); + init_ampl(); + + cview->display_cmd=draw; + + synth=new Synth(synth_top,col,false); +} + +void GreenCtrl::draw(Id id) { + CtrlBase::draw(id); + GreenCtrl *ctr=app->green_control; + BgrWin *cv=ctr->cview; + static XftText txt[2] = { + XftText(cv->win,cv->xft_win,"wave 1",Point(52,68)), + XftText(cv->win,cv->xft_win,"wave 2",Point(116,68)) + }; + for (int i=0;i<2;++i) txt[i].draw(); +} + +BlueCtrl::BlueCtrl(Rect rect,int col): CtrlBase(rect,col) { + attack=new HSlider(cview,Rect(5,TDIST,55,0),FN,0,5,"attack","0","5",slider_cmd,cForeground,Id('blat')); + attack->value()=0; + + decay=new HSlider(cview,Rect(5,52,55,0),FN,0,5,"decay","0","5",slider_cmd,cForeground,Id('blde')); + decay->value()=2; + + rich=new CheckBox(bgwin,Rect(70,10,0,0),FN,cForeground,"rich tone",0); + + chorus=new CheckBox(bgwin,Rect(70,30,0,0),FN,cForeground,"chorus",0); + + dur_limit=new HSlider(cview,Rect(145,TDIST,55,0),FN,0,4,"dur limit","0","4",slider_cmd,cForeground,Id('durl',col)); + set_text(dur_limit->text,"no limit"); + + lowpass=new HSlider(cview,Rect(145,54,55,0),FN,0,4,"lowpass","0","4",slider_cmd,cForeground,Id('lpas',col)); + lowpass->value()=1; + + gr_rect.set(214,70,18,3*TDIST); + + ampl=new HSlider(cview,Rect(145,94,64,0),FN,0,9,"amplitude","0","9",slider_cmd,cForeground,Id('iamp',col)); + init_ampl(); + + mode=new RButWin(cview,Rect(70,70,44,2*TDIST),FN,"mode",false,rbutton_cmd,cForeground,Id('moms',col)); + mode->add_rbut("native"); + mode->add_rbut("msynth"); + + button_style.param=1; + eq=new Button(bgwin,Rect(5,85,28,0),FN,"eq?",button_cmd,Id('eq',col)); + button_style=def_but_st; + + synth=new Synth(synth_top,col,false); +} + +PurpleCtrl::PurpleCtrl(Rect rect,int col): CtrlBase(rect,col) { + int n, + left=43; + static const char *const labels[]={ "1","2","3","6","9" }; + static int init[][harm_max]={{ 3,0,0,1,2 },{ 2,3,3,1,2 }}; + + mode=new RButWin(cview,Rect(4,17,36,2*TDIST),FN,"mode",false,rbutton_cmd,cForeground,Id('moms',col)); + mode->add_rbut("nat"); mode->add_rbut("msyn"); + + button_style.param=1; + eq=new Button(bgwin,Rect(4,50,28,0),FN,"eq?",button_cmd,Id('eq',col)); + button_style=def_but_st; + + for (n=0;nvalue=init[0][n]; + left+=13; + } + set_st_hs_ampl(init[0]); + left+=10; + for (n=0;nvalue=init[1][n]; + left+=13; + } + set_hs_ampl(init[1]); + + (start_dur=new HSlider(cview,Rect(4,96,50,0),FN,0,5,"startup","0","5",slider_cmd,cForeground,Id('pusu')))->value()=2; + + (decay=new HSlider(cview,Rect(58,96,50,0),FN,0,5,"decay","0","5",slider_cmd,cForeground,Id('purd')))->value()=1; + + sound=new RButWin(cview,Rect(193,TDIST,48,4*TDIST),FN,"sound",false,rbutwin_cmd,cForeground,Id('purt')); + sound->add_rbut("clean"); + sound->add_rbut("chorus"); + sound->add_rbut("dist'ed"); + sound->add_rbut("dist'ed~"); + + gr_rect.set(140,82,18,3*TDIST); + + ampl=new HSlider(cview,Rect(172,100,72,0),FN,0,9,"amplitude","0","9",slider_cmd,cForeground,Id('iamp',col)); + init_ampl(); + + cview->display_cmd=draw; + + synth=new Synth(synth_top,col,false); +} + +void just_listen_cmd(Id id,bool val) { + if (output_port!=eJack) return; + if (val) { + if (!wfile_play && !(wfile_play=new JackInterf("Amuc play",play_wfile,'wfd',wf_playing))->okay) wfile_play=0; + } + else { + delete wfile_play; + wfile_play=0; + } +} + +PhmCtrl::PhmCtrl(Rect rect,int col): + CtrlBase(rect,col) { + cview->display_cmd=0; + subview1=new BgrWin(bgwin,Rect(-1,-1,rect.width,rect.height),FN,0,cForeground,1,Id(col,0)); // phys model + subwin1=subview1->win; + (subview2=new BgrWin(bgwin,Rect(-1,-1,rect.width,rect.height),FN,draw,cForeground,1,Id(col,0)))->hide(); // wave file + subwin2=subview2->win; + + mode=new RButWin(subview1,Rect(2,TDIST,70,2*TDIST),FN,"samples",false,rbutton_cmd,cForeground,Id('smod',col)); + mode->add_rbut("phys model"); + mode->add_rbut("wave file"); + mode->add_to(subview2); // mode can be re_parent'ed + + speed_tension=new HVSlider(subview1,Rect(80,TDIST,82,60),32,FN,Int2(1,1),Int2(5,5), + "speed/tension","1","5","1","5",hvslider_cmd,cForeground,Id('spte',col)); + decay=new HSlider(subview1,Rect(170,TDIST,60,0),FN,1,5,"decay","1","5",slider_cmd,cForeground,Id('deca',col)); + + add_noise=new CheckBox(subwin1,Rect(80,76,0,0),FN,cForeground,"dirty",checkbox_cmd,Id('nois',col)); + switch (col) { + case eBlack: + speed_tension->value.set(1,3); decay->value()=4; break; + case eRed: + speed_tension->value.set(2,3); decay->value()=4; break; + case eBlue: + speed_tension->value.set(3,3); decay->value()=4; break; + case eGreen: + speed_tension->value.set(4,3); decay->value()=3; break; + case ePurple: + speed_tension->value.set(4,4); decay->value()=2; break; + case eBrown: + speed_tension->value.set(4,5); decay->value()=1; break; + } + static ChMenuData *file_sel_data=new ChMenuData(); // thus single instance + file_select=new ChMenu(subwin2,Rect(88,TDIST,150,0),FN,show_menu,menu_cmd, + "wave file",Style(1,0,2),cForeground,Id('fsel',col)); + file_select->mbuttons=file_sel_data; + + button_style.set(1,cForeground,0); + new Button(subwin2,Rect(88,38,0,0),FN,"rescan wave files",button_cmd,Id('cwf',col)); + button_style=def_but_st; + + just_listen=new CheckBox(subwin2,Rect(88,60,0,0),FN,cForeground,"just listen",just_listen_cmd); + + wave_ampl=new HSlider(subview1,Rect(2,62,70,0),FN,0,7,"wave ampl","0","7",slider_cmd,cForeground,Id('wamp',col)); + wave_ampl->value()=3; + set_ampl_txt(3); + wave_ampl->add_to(subview2); + + ctrl_pitch=new CheckBox(subwin1,Rect(2,94,0,0),FN,cForeground,"controlled pitch",0); +} + +void PhmCtrl::show_menu(Id id) { + PhmCtrl *pct=col2phm_ctrl(id.id2); + pct->file_select->reset(); + if (wave_buf.fname_nr<0) { // same as button_cmd 'chof', without init_mwin() + if (!wave_buf.sample_dirs->coll_wavefiles()) return; + } + for (int i=0;i<=wave_buf.fname_nr;++i) { + FileName *fn=wave_buf.filenames+i; + pct->file_select->add_mbut(fn->name,fn->col); + } +} + +void PurpleCtrl::draw(Id id) { + CtrlBase::draw(id); + PurpleCtrl *ctr=app->purple_control; + BgrWin *cv=ctr->cview; + static XftText txt[2] = { + XftText(cv->win,cv->xft_win,"startup harm.",Point(45,70)), + XftText(cv->win,cv->xft_win,"sustain harm.",Point(120,70)) + }; + for (int i=0;i<2;++i) txt[i].draw(); +} + +void do_atexit() { if (Xlog) x_widgets_log(); } + +int read_conf_file(const char **warnings,int dim) { // called before init_xwindows() + char *home=getenv("HOME"); + char buf[100]; + snprintf(buf,100,"%s/.amucrc",home); + FILE *conf=fopen(buf,"r"); + if (!conf) { + conf=fopen(buf,"w"); + fputs("# Configuration file for Amuc\n",conf); + fputs("\n# Version of this configuration file\n",conf); + fputs("conf_version = 1.7-A\n",conf); + fputs("\n# Browser called for help menu\n",conf); + fputs("help_browser = epiphany\n",conf); + fputs("# help_browser = firefox\n",conf); + fputs("\n# Nominal font size\n",conf); + fputs("font_size = 10\n",conf); + fputs("\n# MIDI keyboard input mode, choose 'jack' or 'dev'\n",conf); + fputs("midi_mode = jack\n",conf); + fputs("\n# MIDI device if midi_mode = dev\n",conf); + fputs("midi_input = /dev/midi1\n",conf); + fputs("\n# Audio output connection, choose 'jack' or 'alsa'\n",conf); + fputs("output_port = jack\n",conf); + fputs("\n# Output device if output_port = alsa, choose 'default' or e.g. 'hw:0,0'\n",conf); + fputs("pcm_dev_name = default\n",conf); + fclose(conf); + warnings[0]="A default configuration file has been created:"; + warnings[1]=strdup(buf); + return 2; + } + Str str; + str.cmt_ch='#'; + int ind=-1; + for (;;) { + str.rword(conf," ="); + if (str.ch==EOF) break; + if (!str.s[0]) continue; + if (str=="conf_version") { + str.rword(conf," \n"); + if (!(str=="1.7-A")) { + if (indpixel; + xft_Green=xft_calc_color(0,0xC0,0); cGreen=xft_Green->pixel; + xft_Brown=xft_calc_color(0xA0,0x70,0x20); cBrown=xft_Brown->pixel; + cLightGreen=calc_color("#00F000"); + cLightGrey=calc_color("#E5E5E5"); + cBgrGrey=calc_color("#F0F0F0"); + cDarkGrey=calc_color("#7F7F7F"); + cLightBlue=calc_color("#C0FFFF"); + cScopeBgr=calc_color("#D0E7FF"); + + linecol.init(); + def_erb_st.bgcol=calc_color("#F2F2F2"); + slider_style=def_sl_st; + button_style=def_but_st; + ext_rbut_style=def_erb_st; + checkbox_style.set(1,0,0); + init_cursors(); + wave_buf.init(); + + top_win=create_top_window("Amuc",Rect(100,0,view_hmax,view_vmax),true,0,cBackground); + + set_icon(top_win->win,create_pixmap(icon).pm,14,16); + + App ap(inf); // sets app + map_top_window(); + run_xwindows(); + return 0; +} diff --git a/src/amuc.h b/src/amuc.h new file mode 100644 index 0000000..ef30e8e --- /dev/null +++ b/src/amuc.h @@ -0,0 +1,208 @@ +typedef unsigned short ushort; +typedef unsigned int uint; +typedef SafeBuffer ShortBuffer; + +const uint + ePlay=0, + ePlay_x=1, + eSilent=2, + eHi=1, // sharp + eLo=2; // flat +const int + sclin_max=45, // max lines per score + harm_max=5, // harmonics purple instr + mn_bits=14, // multiple notes + tn_bits=7, // tune names + times_max=50, // max parameters of 'time:' command + scope_dim=128; // width of scope window +extern const char + *maj_min_keys[keys_max*4]; + +struct ScoreViewBase; +struct Score; +struct MusicView; +struct ScLine; + +struct ScSection { + uint s_col :4, // eBlack, eRed, ... + cat :2, // ePlay, ePlay_x, eSilent + sign :2, // sharp: eHi, flat: eLo, or 0 + stacc :1, // staccato? + sampled :1, // sampled or physical model note? + sel :1, // selected? + sel_color :1, // selected on color? (only valid if sel == true) + port_dsnr :5, // portando: delta snr + del_start :3, // delay at section start + del_end :3, // delay at section end + s_group :2; // group nr + int port_dlnr :6; // portando: delta lnr + // sum: 31 + uint nxt_note :mn_bits, // = 14, index in mn_buf.buf[] + src_tune :tn_bits; // = 7, index in tnames.buf[] + ScSection(); + void drawSect(ScoreViewBase*,int snr,int lnr); + void drawSect(ScoreViewBase*,ScoreViewBase*,int snr,int lnr); + void drawPlaySect(ScoreViewBase* theV,Point start,Point end,uchar n_sign); + void drawPortaLine(ScoreViewBase* theV,int snr,int lnr,bool erase); + void reset(); + ScSection *nxt(); + ScSection* get_the_section(ScSection *from,int typ); + void set_to_silent(struct ScoreView* sv,int snr,int lnr); + int copy_to_new_sect(int typ,uint *lst_cp); + void rm_duplicates(bool *warn=0); + bool prepend(ScSection *from,int typ,uint *lst_cp); +}; + +struct ScLine { + uchar n_sign; // sharp or flat + ScSection *sect; +}; + +struct Score { + int name; // score-name = tname[name] + int len, // number of sections + lst_sect, // last written section + end_sect, // place of end line + sc_key_nr, // key + signs_mode, // flats or sharps? + sc_meter, // local meter + ngroup; // note group + const int sc_type; // 0 or eMusic + struct ScInfo *scInfo; + ScSection *block; + Arraylin; + RButton *rbut; // located in tunesView + Score(const char *nam,int length,uint sctype); + ~Score(); + void copy(Score*); + void add_copy(Score*,int,int,int,int,int,ScoreViewBase*,int atcolor); + void reset(bool reset_len); + bool check_len(int); + ScSection *get_section(int lnr,int snr); + int get_meas(int snr); + void tone2signs(int); + void copy_keyb_tune(); + void insert_midi_note(int time,int time_end,int lnr,int col,int sign); + void insert_midi_perc(int time,int lnr,int col); +}; + +struct App { + Score *act_tune, + *scoreBuf, + *cur_score; + Str script_file, + input_file, + command, + midi_in_file; + int act_score, // set by score choice buttons + act_color, // set by color choice buttons + act_meter, // set by meter view + act_action, // set by mouse action choice buttons + act_tune_ind, // set by tunes view + act_scope_scale, + act_tempo, // set by tempo slider + mv_key_nr, // score key of musicView + nupq, // note units per quarter note + task, + play_1_col; + bool stop_requested, + repeat; + ExtRButCtrl *active_scoreCtrl; + struct CtrlBase *act_instr_ctrl; + struct FMCtrl *black_control, + *brown_control; + struct RedCtrl *red_control; + struct GreenCtrl *green_control; + struct BlueCtrl *blue_control; + struct PurpleCtrl *purple_control; + struct PhmCtrl *black_phm_control, + *red_phm_control, + *green_phm_control, + *blue_phm_control, + *purple_phm_control, + *brown_phm_control; + struct ChordsWindow *chordsWin; + CheckBox *no_set, + *sampl, + *conn_mk; + struct ScopeView *scopeView; + struct PtrAdjView *ptr_adjust; + struct DialogWin *dia_wd; + App(char *inf); + ~App(); + void playScore(int start,int stop); + bool exec_info_cmd(struct ScInfo&,bool); + bool save(const char*); + bool restore(const char *file,bool add_tunes); + bool read_script(const char *script); + bool save_script(const char *script); + void new_tune(const char *tname); + void copy_tune(const char *tname); + bool run_script(const char *text); + void set_ctrl_sliders(); + void check_reset_as(); + void modify_script(struct EditScript*,int start,int stop); + bool read_tunes(const char*,bool add_tunes); + void unfocus_textviews(); + bool find_score(Score*,struct ScoreView*&); + void scope_insert(int val); + void scope_draw(); + void report_meas(int); + void svplay(struct ScoreView *sv1); + void mplay(); + char *title(); +}; + +struct ScopeView { + Point pt_buf[scope_dim*64]; + int buf[scope_dim*64]; + int nr_samples, + ptr,lst_ptr, + time_scale, + maxi; // max filled index in scope buffer + const int mid; + bool round; + BgrWin *scopeview; + BgrWin *bgwin; + VSlider *scale; + ScopeView(Rect); + static void draw_cmd(Id); + void reset(); + void draw(); + void redraw(); + void insert(int val); + void set_scale(); +}; + +int lnr_to_midinr(int lnr,uint sign); +bool midinr_to_lnr(uchar mnr,uchar& lnr,uchar& sign,int signs_mode); +Score *new_score(const char*); +bool key_name_to_keynr(char *kn,int& key_nr); +const char* ind2tname(int ind); +uint col2color(int col); + +int min(int a,int b); +int max(int a,int b); +int minmax(int a, int x, int b); + +extern const int nop, + signs[][7], + signsMode[keys_max]; +extern bool debug, + midi_mes, + wb_extensions, + i_am_playing, + wf_playing; +extern const int colors[]; +extern Style def_but_st, // default button style, set after init_xwindows() + def_erb_st; // default extern radio button style +extern SliderStyle def_sl_st; + +extern const char + *pcm_dev_name, + *amuc_data, + *wave_samples, + *cur_dir; + +extern ShortBuffer wl_buf; +extern App *app; diff --git a/src/bitmaps.cpp b/src/bitmaps.cpp new file mode 100644 index 0000000..3faf786 --- /dev/null +++ b/src/bitmaps.cpp @@ -0,0 +1,73 @@ +#include +#include "x-widgets.h" +/* +#define up-arrow_width 16 +#define up-arrow_height 16 +#define up-arrow_x_hot 7 +#define up-arrow_y_hot 0 +*/ +static uchar up_arrow_c_bg[] = { + 0xc0, 0x01, 0xc0, 0x01, 0xe0, 0x03, 0xe0, 0x03, 0xf0, 0x07, 0xf0, 0x07, + 0xf8, 0x0f, 0xf8, 0x0f, 0xfc, 0x1f, 0xfc, 0x1f, 0xfe, 0x3f, 0xfe, 0x3f, + 0xe0, 0x03, 0xe0, 0x03, 0xe0, 0x03, 0xe0, 0x03}; +static uchar up_arrow_c[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0xc0, 0x01, 0xe0, 0x03, + 0xe0, 0x03, 0xf0, 0x07, 0xf0, 0x07, 0xf8, 0x0f, 0xf8, 0x0f, 0xc0, 0x01, + 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00}; +static uchar dot_c[] = { + 0x00, 0x00, 0xfe, 0x3f, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, + 0xc2, 0x21, 0xc2, 0x21, 0xc2, 0x21, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, + 0x02, 0x20, 0xfe, 0x3f, 0x00, 0x00, 0x00, 0x00}; +static uchar dot_c_bg[] = { + 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, + 0xc7, 0x71, 0xc7, 0x71, 0xc7, 0x71, 0x07, 0x70, 0x07, 0x70, 0x07, 0x70, + 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x00}; +static const uchar cross_c[] = { // cross cursor + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0xff, 0x7f, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00}; +static const uchar *cross_c_bg=cross_c; +static uchar text_c_bits[] = { + 0x00, 0x00, 0x1e, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x00, + 0x0c, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00}; +static uchar text_c_bg_bits[] = { + 0x3f, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0x1e, 0x00, 0x1e, 0x00, 0x1e, 0x00, + 0x1e, 0x00, 0x1e, 0x00, 0x1e, 0x00, 0x1e, 0x00, 0x1e, 0x00, 0x1e, 0x00, + 0x3f, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0x00, 0x00}; +static const char + *icon_data[] = { + "16 18 3 1", + "# c #ff0000", + ". c #ffffff", + "* c #000000", + "****************", + "*...........##.*", + "*.........##.#.*", + "*.......##...#.*", + "*.....##....##.*", + "*.....#...##.#.*", + "*.....#.##...#.*", + "*.....##.....#.*", + "*.....#......#.*", + "*.....#......#.*", + "*.....#...####.*", + "*.....#..#####.*", + "*.....#..#####.*", + "*..####...###..*", + "*.#####........*", + "*.#####........*", + "*..###.........*", + "****************" + }; + +uint up_arrow_cursor, dot_cursor, cross_cursor, text_cursor; +const char **icon; + +void init_cursors() { + up_arrow_cursor=create_cursor(up_arrow_c,up_arrow_c_bg,16,16,Point(7,0),false); + dot_cursor=create_cursor(dot_c,dot_c_bg,16,16,Point(7,7),true); + cross_cursor=create_cursor(cross_c,cross_c_bg,16,16,Point(7,7),true); + text_cursor=create_cursor(text_c_bits,text_c_bg_bits,16,16,Point(2,7),true); + icon=icon_data; +} diff --git a/src/bitmaps.h b/src/bitmaps.h new file mode 100644 index 0000000..5042018 --- /dev/null +++ b/src/bitmaps.h @@ -0,0 +1,3 @@ +extern uint up_arrow_cursor, dot_cursor, cross_cursor, text_cursor; +extern const char **icon; +void init_cursors(); diff --git a/src/cai_xwidgets.h b/src/cai_xwidgets.h new file mode 100644 index 0000000..61be336 --- /dev/null +++ b/src/cai_xwidgets.h @@ -0,0 +1,5 @@ +#include +#include + +cairo_surface_t *cai_get_surface(BgrWin *bgr); +cairo_pattern_t *col2cai_col(uint col); diff --git a/src/chords.cpp b/src/chords.cpp new file mode 100644 index 0000000..0e2fc07 --- /dev/null +++ b/src/chords.cpp @@ -0,0 +1,280 @@ +#include +#include +#include +#include + +#include +#include "amuc-headers.h" + +static const char *chords_and_scales="chords-and-scales"; +const char + *sharp[] = { + "6 10 2 1", + ". c #ffffff", + "# c #0000ff", + ".#..#.", + ".#..##", + ".####.", + "##..#.", + ".#..#.", + ".#..##", + ".####.", + "##..#.", + ".#..#.", + ".#..#." + }, + *flat[] = { + "6 10 2 1", + "* c #0000ff", + ". c #ffffff", + "*.....", + "*.....", + "*.....", + "*.....", + "*.***.", + "**...*", + "*....*", + "*...*.", + "*.**..", + "**...." + }; + + +static const char + *nr_signs[keys_max] = { + 0,"5","2","3","4","1","6","6","1","4","3","2","5" + }; +static Pixmap2 + nul, + *sign, + sharp_pm, + flat_pm; + +void keys_text(uint win,XftDraw *xft_win,Id,int n,int) { + int ind=keys_max-1-n; + xft_draw_string(xft_win,xft_Black,Point(4,n*TDIST+10),maj_min_keys[ind]); + xft_draw_string(xft_win,xft_Black,Point(34,n*TDIST+10),maj_min_keys[ind+keys_max]); + if (ind > 0) { + xft_draw_string(xft_win,xft_Blue,Point(64,n*TDIST+10),nr_signs[ind]); + draw_pixmap(win,Point(74,n*TDIST),sign[ind],6,10); + } +} + +ChordsWindow::ChordsWindow(Point top): + the_key_nr(0),dm(eScDep_no),shift_12(false) { + const int dx=300, + dy=430; + subw=new SubWin("keys, chords, scales",Rect(top.x,top.y,dx,dy),true,cForeground,0,hide_chwin); + chwin=subw->win; xft_chwin=subw->xft_win; + + sharp_pm=create_pixmap(sharp); + flat_pm=create_pixmap(flat); + static Pixmap2 sgn[keys_max]= { + nul,flat_pm,sharp_pm,flat_pm,sharp_pm,flat_pm,sharp_pm,flat_pm,sharp_pm,flat_pm,sharp_pm,flat_pm,sharp_pm + }; + sign=sgn; + + keys=new RButWin(subw,Rect(0,TDIST,86,keys_max*TDIST),FN,"keys",false,base_cmd,cForeground); + but1=new Button(chwin,Rect(2,212,48,0),FN,"set key",set_key); + but2=new Button(chwin,Rect(52,212,58,0),FN,"force key",force_key); + get_scales=new Button(chwin,Rect(120,212,72,0),FN,"read scales",read_scales); + dep_mode=new CheckBox(chwin,Rect(210,212,0,15),FN,cForeground,"local dep.",depm_cmd); + tone_nrs=new EditWin(chwin,Rect(90,TDIST,17,keys_max*TDIST),FN,false,0); + scales=new RButWin(subw,Rect(120,TDIST,164,190),FN,"scales, chords",false,scales_cmd,cForeground); + scroll=new VScrollbar(chwin,Rect(286,TDIST,0,190),FN,0,scr_cmd); + for (int n=0;;++n) { + RButton *rbut=keys->add_rbut(keys_text); + if (n==keys_max-1) { + keys->set_rbut(rbut,false); + break; + } + } + draw_tone_numbers(); + circle=new BgrWin(chwin,Rect(50,235,190,190),FN,draw_circ,cForeground,0,Id(0,0)); +} + +void ChordsWindow::draw_circ(Id id) { + ChordsWindow *chw=app->chordsWin; + BgrWin *bgwin=chw->circle; + static Point mid(95,95); + static int butnr_to_i[13]={ 0,5,10,3,8,1,6,6,11,4,9,2,7 }, + angle_to_i[12]={ 0,5,11,3,9,1,7,12,4,10,2,8 }; + static Point + dot[12], + label[12]; + static bool init=false; + int i; + bool draw_dots=id.id2; // only update dot's? + if (!init) { + init=true; + for (i=0;i<12;++i) { + dot[i].set(mid.x+int(60*sin(i*M_PI/6)),mid.y-int(60*cos(i*M_PI/6))); + label[i].set(mid.x-6+int(80*sin(i*M_PI/6)),int(mid.y-80*cos(i*M_PI/6))); + } + } + if (draw_dots) + for (i=0;i<12;++i) + fill_circle(bgwin->win,1,i==butnr_to_i[12-chw->keys->act_rbutnr()] ? cRed : cForeground,dot[i],4); + else { + bgwin->clear(); + cairo_t *cr=cairo_create(cai_get_surface(bgwin)); + cairo_set_line_width(cr,2); + cairo_set_source(cr,cai_Border); + cairo_arc(cr,mid.x+0.5,mid.y+0.5,60,0,2*M_PI); + cairo_stroke(cr); + cairo_set_line_width(cr,2); + for (i=0;i<12;++i) { + fill_circle(bgwin->win,1,i==butnr_to_i[12-chw->keys->act_rbutnr()] ? cRed : cForeground,dot[i],5); + cairo_arc(cr,dot[i].x+0.5,dot[i].y+0.5,5,0,2*M_PI); + cairo_stroke(cr); + xft_draw_string(bgwin->xft_win,xft_Black,label[i],maj_min_keys[angle_to_i[i]]); + if (i>0) { + int ind=angle_to_i[i]; + xft_draw_string(bgwin->xft_win,xft_Blue,Point(label[i].x,label[i].y+12),nr_signs[ind]); + draw_pixmap(bgwin->win,Point(label[i].x+10,label[i].y+2),sign[ind],6,10); + } + } + cairo_destroy(cr); + } +} + +void ChordsWindow::base_cmd(Id,int nr,int) { // nr between 0 and keys_max-1 + ChordsWindow *cw=app->chordsWin; + cw->the_key_nr=keys_max-1-nr; + cw->draw_tone_numbers(); + cw->upd_scoreview(cw->scales->act_rbutnr()); + cw->draw_circ(Id(0,1)); +} + +void ChordsWindow::depm_cmd(Id,bool val) { + ChordsWindow *cw=app->chordsWin; + if (cw->dm!=eScDep_no) cw->dm= val ? eScDep_lk : eScDep_sk; +} + +void ChordsWindow::scales_cmd(Id,int nr,int) { + static int prev_nr, + dmode,prev_dmode; + ChordsWindow *cw=app->chordsWin; + dmode=cw->scale_data[cw->scales->act_rbutnr()].depmode; + if (prev_dmode!=dmode) { + if (dmode==eScDep_lk) { cw->dep_mode->set_cbval(true,0); cw->dm=eScDep_lk; } + else if (dmode==eScDep_sk) { cw->dep_mode->set_cbval(false,0); cw->dm=eScDep_sk; } + else cw->dm=eScDep_no; + } + prev_dmode=dmode; + cw->shift_12= prev_nr==nr ? !cw->shift_12 : false; // if gt12, then shift start of chord 1 octave? + prev_nr=nr; + cw->upd_scoreview(nr); +} + +void ChordsWindow::hide_chwin(Id) { + hide_window(app->chordsWin->chwin); +} +void ChordsWindow::chord_or_scale_name(char *buf,int sc_key_nr) { // returns temporary string + if (!scales->act_button) { + alert("chord_or_scale_name: scales not yet read"); + return; + } + int key_nr= dm==eScDep_lk ? the_key_nr : sc_key_nr; + switch (scale_data[scales->act_rbutnr()].depmode) { + case eScDep_lk: + snprintf(buf,50,"chord: %s%s", + maj_min_keys[key_nr], // e.g. "Db" + scales->act_button->label.txt+1); // e.g. "C7", skip 'C' + break; + case eScDep_sk: + snprintf(buf,50,"scale: %s %s", + maj_min_keys[key_nr], // e.g. "Db" + scales->act_button->label.txt); // e.g. "major" + break; + default: + strncpy(buf,scales->act_button->label.txt,50); // e.g. "white keys" + } +} + +int keynr2ind(int key_nr) { // key_nr -> array index + static int ind[keys_max] = { 0,1,2,3,4,5,6,6,7,8,9,10,11 }; + return ind[key_nr]; +} + +void ChordsWindow::draw_tone_numbers() { + int n; + static const char *nrs[8]={ 0,"1","2","3","4","5","6","7" }; + static int toneNrs[13][keys_max] = { + // C D E F G A B <-- C major + { 1,0,2,0,3,4,0,0,5,0,6,0,7 }, // C + { 7,1,0,2,0,3,4,4,0,5,0,6,0 }, // Des + { 0,7,1,0,2,0,3,3,0,4,5,0,6 }, // D + { 6,0,7,1,0,2,0,0,3,4,0,5,0 }, // Es + { 0,6,0,7,1,0,2,2,0,3,4,0,5 }, // E + { 5,0,6,0,7,1,0,0,2,0,3,4,0 }, // F + { 0,5,0,6,0,7,1,1,0,2,0,3,4 }, // Fis + { 0,5,0,6,0,7,1,1,0,2,0,3,4 }, // Ges + { 4,0,5,0,6,0,7,7,1,0,2,0,3 }, // G + { 3,4,0,5,0,6,0,0,7,1,0,2,0 }, // As + { 0,3,4,0,5,0,6,6,0,7,1,0,2 }, // A + { 2,0,3,4,0,5,0,0,6,0,7,1,0 }, // Bes + { 0,2,0,3,4,0,5,5,0,6,0,7,1 }, // B + }; + tone_nrs->reset(); + int *act_tone_nrs=toneNrs[the_key_nr]; + for (n=0;nset_line((char*)nrs[act_tone_nrs[n]],keys_max-1-n); +} + +bool ChordsWindow::read_scalef(const char *dname) { + char buf[max100]; + FILE *fp; + Str str; + int n,nr; + XftColor *text_col; + snprintf(buf,max100,"%s/%s",dname,chords_and_scales); + if ((fp=fopen(buf,"r")) == 0) return false; + scales->empty(); + scroll->value=0; + str.cmt_ch='#'; + for (nr=0;;++nr) { + str.rword(fp," \t\"\n"); + if (!str.s[0]) { + if (str.ch=='"') { // quoted? + str.rword(fp,"\"\n"); + if (str.ch=='"') fgetc(fp); + else { alert("missing '\"'"); return true; } + } + else break; + } + for (char *p=str.s;*p;++p) if (*p=='\t') *p=' '; // replace tabs + scale_data[nr].name=strndup(str.s,ScaleData::nmax); + for (n=0;n<24;++n) scale_data[nr].valid[n]=false; + scale_data[nr].gt12=false; + scale_data[nr].depmode=eScDep_no; + text_col=xft_Black; + for (;;) { + str.rword(fp,", \t\n"); + n=atoi(str.s); + if (n>24) { alert("note nr %d (should be < 24)",n); return true; } + if (n>12) scale_data[nr].gt12=true; + scale_data[nr].valid[n]=true; + if (strchr("\n \t",str.ch)) break; + } + if (str.ch!='\n') { + str.rword(fp," \t\n"); + if (str=="no") scale_data[nr].depmode=eScDep_no; + else if (str=="chord") { scale_data[nr].depmode=eScDep_lk; text_col=xft_Blue; } + else if (str=="scale") { scale_data[nr].depmode=eScDep_sk; text_col=xft_Red; } + else { + alert("scale/chord type should be 'no', 'chord' or 'scale'"); + return true; + } + } + scales->add_rbut(scale_data[nr].name,text_col); + scroll->set_range((scales->buttons->butnr+2)*TDIST); + } + return true; +} + +void ChordsWindow::read_scales(Id) { + if (!app->chordsWin->read_scalef(cur_dir) && !app->chordsWin->read_scalef(amuc_data)) + alert("file %s not found in . or in %s",chords_and_scales,amuc_data); +} diff --git a/src/chords.h b/src/chords.h new file mode 100644 index 0000000..776a2ff --- /dev/null +++ b/src/chords.h @@ -0,0 +1,50 @@ +enum { + eScDep_no, // scale is constant + eScDep_lk, // scale depends on local key + eScDep_sk // scale depends on score key +}; + +struct ScaleData { + static const int nmax=30; + char *name; + bool valid[24], + gt12; + int depmode; // dependancy on local or score key +}; + +struct ChordsWindow { + SubWin *subw; + BgrWin *circle; + RButWin *keys, + *scales; + EditWin *tone_nrs; + Button *but1,*but2, + *get_scales; + CheckBox *dep_mode; + VScrollbar *scroll; + Arrayscale_data; + int the_key_nr, + dm; + bool shift_12; + uint chwin; + XftDraw *xft_chwin; + static void del_chwin(Id); + static void quit_chwin(Id); + static void hide_chwin(Id); + static void set_key(Id); + static void read_scales(Id); + static void force_key(Id); + static void base_cmd(Id,int nr,int fire); + static void scales_cmd(Id,int nr,int fire); + static void scr_cmd(Id,int val,int,bool); + static void depm_cmd(Id,bool); + static void draw_circ(Id); + ChordsWindow(Point top); + void draw_tone_numbers(); + bool read_scalef(const char *fname); + void chord_or_scale_name(char *buf,int sc_key_nr); + void upd_chordname(struct ScoreView *sv); + void upd_scoreview(int scales_nr); +}; + +extern int keynr2ind(int key_nr); diff --git a/src/colors.h b/src/colors.h new file mode 100644 index 0000000..edb1294 --- /dev/null +++ b/src/colors.h @@ -0,0 +1,21 @@ +// used by all .cpp files +typedef unsigned int uint; + +enum Color { + eBlack,eRed,eBlue,eGreen,ePurple,eBrown, // <-- used by instruments + eGrey +}; + +extern int SAMPLE_RATE; + +const int + colors_max=6, + groupnr_max=3, // max color group-nr + keys_max=13, + max100=100, // size of char buffers + max200=200; +const uint + ampl_max=9; + +extern const char *const color_name[colors_max]; +int color_nr(const char *coln,bool *ok=0); diff --git a/src/dance.sco b/src/dance.sco new file mode 100644 index 0000000..f8a05ac --- /dev/null +++ b/src/dance.sco @@ -0,0 +1,15 @@ +4m8 +7m0e24g0k5 "solo1" 2L17N1d1i0s1c3g0 2L17N2d1i0s1c3g0 2L17N3d1i0s1c3g0 2L27N4d1i0s0c3g0 2L43N4d1i0s2c1g0 2L26N6d1i0s0c3g0 2L41N6d1i0s2c5g0 2L25N8d1i0s0c3g0 2L43N8d1i0s2c1g0 2L24N10d1i0s0c3g0 2L41N10d1i0s2c5g0 2L23N12d1i0s0c3g0 2L43N12d1i0s2c1g0 2L21N13d2i2s0c3g0 2L41N14d1i0s2c5g0 2L20N16d1i0s0c3g0 2L43N16d1i0s2c1g0 +7m0e16g0k5 "solo2" 2L17N5d1i0s0c2g0 2L20N5d1i0s0c2g0 2L19N6d1i0s0c2g0 2L22N6d1i0s0c2g0 2L21N7d1i2s0c2g0 2L24N7d1i0s0c2g0 2L20N8d1i0s0c2g0 2L23N8d1i0s0c2g0 2L23N10d1i0s0c2g0 2L26N10d1i0s0c2g0 2L25N11d1i0s0c2g0 2L28N11d1i2s0c2g0 2L22N12d1i0s0c2g0 2L24N12d1i0s0c2g0 2L22N14d1i0s0c2g0 2L25N14d1i0s0c2g0 +7m0e0g0k5 "solo3" 2L18N1d1i0s1c3g0 2L18N2d1i0s1c3g0 2L18N3d1i0s1c3g0 2L20N4d1i0s0c3g0 2L22N6d1i0s0c3g0 2L23N8d1i0s0c3g0 2L24N10d1i0s0c3g0 2L25N12d1i0s0c3g0 2L26N14d1i0s0c3g0 2L27N16d1i0s0c3g0 2L30N18d1i0s0c3g0 2L28N20d2i2s0c3g0 2L35N20d2i2s0c3g0 2L30N22d1i0s0c3g0 2L28N24d2i0s0c3g0 2L36N24d2i0s0c3g0 2L30N26d1i0s0c3g0 2L27N28d1i0s0c3g0 2L38N28d1i0s0c3g0 2L26N30d1i0s0c3g0 2L37N30d1i0s0c3g0 2L25N32d2i0s0c3g0 2L32N32d2i0s0c3g0 +7m0e32g0k5 "solo4" 2L17N0d1i0s0c2g0 2L20N0d1i0s0c2g0 2L15N2d1i0s0c2g0 2L17N2d1i0s0c2g0 2L19N4d1i0s0c2g0 2L23N4d1i0s0c2g0 2L17N6d1i0s0c2g0 2L19N6d1i0s0c2g0 2L22N8d1i0s0c2g0 2L27N8d1i0s0c2g0 2L19N10d1i0s0c2g0 2L21N10d1i2s0c2g0 2L24N12d2i0s0c2g0 2L27N12d2i0s0c2g0 2L19N16d1i0s1c2g0 2L21N16d1i2s0c2g0 2L19N17d1i0s1c2g0 2L22N17d1i0s0c2g0 2L19N18d1i0s0c2g0 2L23N18d1i0s0c2g0 2L19N20d1i0s0c2g0 2L21N20d1i2s0c2g0 2L19N22d1i0s1c2g0 2L21N22d1i2s0c2g0 2L18N23d1i0s1c2g0 2L20N23d1i0s0c2g0 2L17N24d1i0s0c2g0 2L19N24d1i0s0c2g0 2L21N24d1i2s0c2g0 2L17N26d1i0s0c2g0 2L20N26d1i0s0c2g0 2L22N26d1i0s0c2g0 +7m0e16g0k5 "solo5" 2L25N0d2i0s0c2g0 2L27N0d2i0s0c2g0 2L31N2d1i0s0c2g0 2L33N2d1i0s0c2g0 2L27N4d2i0s0c2g0 2L30N4d2i0s0c2g0 2L25N7d1i0s0c2g0 2L18N8d1i0s0c2g0 2L20N10d3i0s0c2g0 2L23N10d3i0s0c2g0 2L25N10d3i0s0c2g0 +7m0e48g0k5 "solo6" 2L17N0d4i0s0c2g0 2L19N4d4i0s0c2g0 2L43N4d1i0s2c2g0 2L18N8d4i0s0c2g0 2L20N12d4i0s0c2g0 2L43N12d1i0s2c2g0 2L19N16d4i0s0c2g0 2L21N20d4i2s0c2g0 2L43N20d1i0s2c2g0 2L20N24d4i0s0c2g0 2L24N28d2i0s0c2g0 2L43N28d1i0s2c2g0 2L23N32d6i0s0c2g0 2L43N36d1i0s2c2g0 2L19N38d2i0s0c2g0 2L22N40d3i0s0c2g0 +7m0e56g0k5 "solo7" 2L22N0d4i0s0c2g0 2L24N4d4i0s0c2g0 2L43N4d1i0s2c5g0 2L43N6d1i0s2c5g0 2L23N8d4i0s0c2g0 2L25N12d4i0s0c2g0 2L43N12d1i0s2c5g0 2L43N14d1i0s2c5g0 2L24N16d4i0s0c2g0 2L26N20d4i0s0c2g0 2L43N20d1i0s2c5g0 2L43N22d1i0s2c5g0 2L25N24d4i0s0c2g0 2L29N28d2i0s0c2g0 2L43N28d1i0s2c5g0 2L31N32d4i0s0c2g0 2L43N32d1i0s2c0g0 2L30N38d1i0s0c2g0 2L29N40d3i0s0c2g0 2L43N40d1i0s2c0g0 2L27N44d3i0s0c2g0 2L29N48d3i0s0c2g0 2L43N48d1i0s2c0g0 2L28N51d1i2s0c2g0 2L29N52d1i0s0c2g0 2L28N53d1i2s0c2g0 2L29N54d2i0s0c2g0 +7m0e32g0k5 "solo8" 2L17N0d1i0s0c2g0 2L19N0d1i0s0c2g0 2L17N2d1i0s0c2g0 2L19N2d1i0s0c2g0 2L15N6d1i0s0c2g0 2L19N8d2i0s0c2g0 2L21N8d2i2s0c2g0 2L17N14d1i0s0c2g0 2L22N16d1i0s0c2g0 2L24N16d1i0s0c2g0 2L22N18d1i0s0c2g0p-3,1 2L24N18d1i0s0c2g0p-3,1 2L19N20d2i0s0c2g0 2L21N20d2i2s0c2g0 2L24N24d4i0s0c2g0 2L27N24d4i0s0c2g0 +7m0e56g0k0 "drums2" 2L39N0d1i0s2c0g0 2L39N4d1i0s2c5g0 2L39N6d1i0s2c5g0 2L39N16d1i0s2c0g0 2L39N20d1i0s2c5g0 2L39N22d1i0s2c5g0 2L39N32d1i0s2c0g0 2L39N36d1i0s2c5g0 2L39N38d1i0s2c5g0 2L39N48d1i0s2c0g0 2L39N52d1i0s2c5g0 2L39N54d1i0s2c5g0 +7m0e16g0k5 "bass2" 2L31N0d3i0s0c5g0 2L33N4d3i0s0c5g0 2L31N8d1i0s0c5g0 2L30N10d3i0s0c5g0 2L27N12d3i0s0c5g0 +7m0e16g0k5 "bass2a" 2L29N0d3i0s0c5g0 2L33N4d3i0s0c5g0 2L31N8d1i0s0c5g0 2L27N10d3i0s0c5g0 2L29N12d3i0s0c5g0 +7m0e64g0k0 "bass3" 2L27N0d8i0s0c5g0 2L34N0d8i0s0c5g0 2L35N11d1i2s0c5g0 2L34N12d1i0s0c5g0 2L32N13d1i0s0c5g0 2L33N14d1i0s0c5g0 2L31N15d1i0s0c5g0 2L25N16d8i0s0c5g0 2L29N16d8i0s0c5g0 2L28N27d1i2s0c5g0 2L29N28d1i0s0c5g0 2L30N29d1i0s0c5g0 2L28N30d1i2s0c5g0 2L27N31d1i0s0c5g0 2L22N32d8i0s0c5g0 2L25N32d8i0s0c5g0 2L24N43d1i0s0c5g0 2L26N44d1i0s0c5g0 2L24N45d1i0s0c5g0 2L25N46d1i0s0c5g0 2L22N47d1i0s0c5g0 2L18N48d2i0s0c5g0 2L20N48d2i0s0c5g0 2L18N52d8i0s0c5g0 2L20N52d8i0s0c5g0 +7m0e48g0k5 "bass5" 2L33N0d1i0s0c5g0 2L34N2d1i0s0c5g0 2L33N4d1i0s0c5g0 2L35N6d1i2s0c5g0 2L33N8d1i0s0c5g0 2L34N10d1i0s0c5g0 2L33N12d1i0s0c5g0 2L35N14d1i2s0c5g0 2L33N16d1i0s0c5g0 2L34N18d1i0s0c5g0 2L33N20d1i0s0c5g0 2L35N22d1i2s0c5g0 2L33N24d1i0s0c5g0 2L34N26d1i0s0c5g0 2L33N28d1i0s0c5g0 2L35N30d1i2s0c5g0 2L30N32d2i0s0c5g0 2L32N38d1i0s0c5g0 2L33N40d3i0s0c5g0 +7m0e24g0k5 "bass6" 2L34N0d2i0s0c5g0 2L33N6d1i0s0c5g0 2L31N8d2i0s0c5g0 2L34N12d2i0s0c5g0 2L33N16d6i0s0c5g0 diff --git a/src/dance.scr b/src/dance.scr new file mode 100644 index 0000000..2a407f8 --- /dev/null +++ b/src/dance.scr @@ -0,0 +1,41 @@ +set tempo:120 +out-par key:F nupq:4 +# green +set green time:0,52 attack:0 decay:1 wave1:3,3 wave2:1,2 freq-ratio:2 +set green time:45 wave1:1,2 wave2:3,2 freq-ratio:1 +set green time:0,4.1,45 rev:0 ampl:6 pan:L +set green time:2.1,26.1,54.1 pan:R rev:3,0 +# black +set black fm:2,4 attack:0 decay:1 ampl:6 mm:1,1 +set black phys-mod:1,3,4,1 wa:5 +# brown +# bass with portamento +set brown msynth:F5,41100,510,504,6030010,340,16470,4,065705,75010,6 +set brown fm:0,6 dt:2 at:1 dc:3 mm:2,1 ampl:5 pan:L +set brown time:30 mode:nat time:0,58 mode:msynth time:58 ampl:6 +set brown phys-mod:4,5,2,0 wa:4 +# blue +set blue attack:0 decay:2 rich:on chorus:off lp:0 ampl:5 pan:R +set blue time:60 attack:1 decay:3 chorus:on ampl:5 +# red +set red start-wave:4,4 sustain-wave:3,3 startup:2 decay:2 ampl:6 + +add solo1 time:0,2,4,24,26,52,54 +add solo2 time:6,28,56 +add solo3 time:45 +add solo4 time:8 +add solo4 raise:-3 time:12 +add solo4 raise:2 time:16 +add solo8 time:20 +add solo5 time:49.4 +add drums2 time:38 +add bass2 time:8,10,12,14,20,22 +add bass2a time:16,18 +add bass3 time:30 +add bass3 to:7 time:38 +add solo6 time:60 +add solo7 time:66 +add bass5 to:2 time:58 +add bass5 time:60 +add bass5 to:4 time:66 +add bass6 time:70 diff --git a/src/dump-wav.cpp b/src/dump-wav.cpp new file mode 100644 index 0000000..4920d8d --- /dev/null +++ b/src/dump-wav.cpp @@ -0,0 +1,95 @@ +/* +Using buffered files. +Original authors: + Sed Barbouky + Christian Klein +*/ +#include +#include "dump-wav.h" + +static bool inited=false; +static FILE *dumpfd; +static int size; +static const int sample_rate=44100; + +// return false if error +bool init_dump_wav(const char *fname) +{ + short dum16; + uint dum32; + + inited=false; + if ((dumpfd=fopen(fname,"w"))==0) return false; + if ( + fwrite("RIFF", 4,1,dumpfd)!=1 || + (dum32=36, fwrite(&dum32, 4,1,dumpfd)!=1) || // header size + fwrite("WAVEfmt ", 8,1,dumpfd)!=1 || + (dum32=16, fwrite(&dum32, 4,1,dumpfd)!=1) || // chunk size + (dum16=1, fwrite(&dum16, 2,1,dumpfd)!=1) || // format tag (1 = uncompressed PCM) + (dum16=2, fwrite(&dum16, 2,1,dumpfd)!=1) || // no of channels + (dum32=sample_rate, fwrite(&dum32, 4,1,dumpfd)!=1) || // rate + (dum32=sample_rate*2*2, fwrite(&dum32, 4,1,dumpfd)!=1) || // average bytes/sec + (dum16=(2*16+7)/8, fwrite(&dum16, 2,1,dumpfd)!=1) || // block align + (dum16=16, fwrite(&dum16, 2,1,dumpfd)!=1) || // bits per sample + fwrite("data", 4,1,dumpfd)!=1 || + (dum32=0, fwrite(&dum32, 4,1,dumpfd)!=1)) { // sample length (0 for now) + fclose(dumpfd); + return false; + } + inited=true; + size=36; + return true; +} + +bool close_dump_wav(void) { + if (!inited) return false; + inited=false; + + // update the wav header + fseek(dumpfd, 4, SEEK_SET); // first place to update + if (fwrite(&size, 4,1,dumpfd)!=1) goto error; + size-=36; + fseek(dumpfd, 40, SEEK_SET); // second place + if (fwrite(&size, 4,1,dumpfd)!=1) goto error; + + fclose(dumpfd); + return true; + + error: + fclose(dumpfd); + return false; +} + +bool dump_wav(char *buf, int sz) { + if (!inited) return false; + if (fwrite(buf, sz,1,dumpfd)!=1) { + fclose(dumpfd); + return false; + } + size+=sz; + return true; +} +#ifdef TEST_DUMPWAV +#include +#define NB_SAMPLE 2048 +int main() { + int n,m,res; + short buf[NB_SAMPLE], + val1,val2; + res=init_dump_wav("out.wav"); + printf("init:%d\n",res); + for (n=val1=val2=0;n<30;++n) { + for (m=0;m +#include +#include +#include +#include "amuc-headers.h" + +const int perc_lo=27, + perc_hi=87; + +struct GM_instr { + int gm_nr; + const char *name; + int col; +}; + +static GM_instr gm_instr[128]= { +{ 1, "Acoustic Grand Piano", 0 }, +{ 2, "Bright Acoustic Piano", 0 }, +{ 3, "Electric Grand Piano", 0 }, +{ 4, "Honky-tonk Piano", 0 }, +{ 5, "Electric Piano 1", 0 }, +{ 6, "Electric Piano 2", 0 }, +{ 7, "Harpsichord", 0 }, +{ 8, "Clavi", 0 }, +{ 9, "Celesta", 0 }, +{ 10, "Glockenspiel", 0 }, +{ 11, "Music Box", 0 }, +{ 12, "Vibraphone", 0 }, +{ 13, "Marimba", 0 }, +{ 14, "Xylophone", 0 }, +{ 15, "Tubular Bells", 0 }, +{ 16, "Dulcimer", 0 }, +{ 17, "Drawbar Organ", 0 }, +{ 18, "Percussive Organ", 0 }, +{ 19, "Rock Organ", 0 }, +{ 20, "Church Organ", 0 }, +{ 21, "Reed Organ", 0 }, +{ 22, "Accordion", 0 }, +{ 23, "Harmonica", 0 }, +{ 24, "Tango Accordion", 0 }, +{ 25, "Acoustic Guitar (nylon)", 0 }, +{ 26, "Acoustic Guitar (steel)", 0 }, +{ 27, "Electric Guitar (jazz)", 0 }, +{ 28, "Electric Guitar (clean)", 0 }, +{ 29, "Electric Guitar (muted)", 0 }, +{ 30, "Overdriven Guitar", 0 }, +{ 31, "Distortion Guitar", 0 }, +{ 32, "Guitar harmonics", 0 }, +{ 33, "Acoustic Bass", 0 }, +{ 34, "Electric Bass (finger)", 0 }, +{ 35, "Electric Bass (pick)", 0 }, +{ 36, "Fretless Bass", 0 }, +{ 37, "Slap Bass 1", 0 }, +{ 38, "Slap Bass 2", 0 }, +{ 39, "Synth Bass 1", 0 }, +{ 40, "Synth Bass 2", 0 }, +{ 41, "Violin", 0 }, +{ 42, "Viola", 0 }, +{ 43, "Cello", 0 }, +{ 44, "Contrabass", 0 }, +{ 45, "Tremolo Strings", 0 }, +{ 46, "Pizzicato Strings", 0 }, +{ 47, "Orchestral Harp", 0 }, +{ 48, "Timpani", 0 }, +{ 49, "String Ensemble 1", 0 }, +{ 50, "String Ensemble 2", 0 }, +{ 51, "SynthStrings 1", 0 }, +{ 52, "SynthStrings 2", 0 }, +{ 53, "Choir Aahs", 0 }, +{ 54, "Voice Oohs", 0 }, +{ 55, "Synth Voice", 0 }, +{ 56, "Orchestra Hit", 0 }, +{ 57, "Trumpet", 0 }, +{ 58, "Trombone", 0 }, +{ 59, "Tuba", 0 }, +{ 60, "Muted Trumpet", 0 }, +{ 61, "French Horn", 0 }, +{ 62, "Brass Section", 0 }, +{ 63, "SynthBrass 1", 0 }, +{ 64, "SynthBrass 2", 0 }, +{ 65, "Soprano Sax", 0 }, +{ 66, "Alto Sax", 0 }, +{ 67, "Tenor Sax", 0 }, +{ 68, "Baritone Sax", 0 }, +{ 69, "Oboe", 0 }, +{ 70, "English Horn", 0 }, +{ 71, "Bassoon", 0 }, +{ 72, "Clarinet", 0 }, +{ 73, "Piccolo", 0 }, +{ 74, "Flute", 0 }, +{ 75, "Recorder", 0 }, +{ 76, "Pan Flute", 0 }, +{ 77, "Blown Bottle", 0 }, +{ 78, "Shakuhachi", 0 }, +{ 79, "Whistle", 0 }, +{ 80, "Ocarina", 0 }, +{ 81, "Lead 1 (square)", 0 }, +{ 82, "Lead 2 (sawtooth)", 0 }, +{ 83, "Lead 3 (calliope)", 0 }, +{ 84, "Lead 4 (chiff)", 0 }, +{ 85, "Lead 5 (charang)", 0 }, +{ 86, "Lead 6 (voice)", 0 }, +{ 87, "Lead 7 (fifths)", 0 }, +{ 88, "Lead 8 (bass + lead)", 0 }, +{ 89, "Pad 1 (new age)", 0 }, +{ 90, "Pad 2 (warm)", 0 }, +{ 91, "Pad 3 (polysynth)", 0 }, +{ 92, "Pad 4 (choir)", 0 }, +{ 93, "Pad 5 (bowed)", 0 }, +{ 94, "Pad 6 (metallic)", 0 }, +{ 95, "Pad 7 (halo)", 0 }, +{ 96, "Pad 8 (sweep)", 0 }, +{ 97, "FX 1 (rain)", 0 }, +{ 98, "FX 2 (soundtrack)", 0 }, +{ 99, "FX 3 (crystal)", 0 }, +{ 100, "FX 4 (atmosphere)", 0 }, +{ 101, "FX 5 (brightness)", 0 }, +{ 102, "FX 6 (goblins)", 0 }, +{ 103, "FX 7 (echoes)", 0 }, +{ 104, "FX 8 (sci-fi)", 0 }, +{ 105, "Sitar", 0 }, +{ 106, "Banjo", 0 }, +{ 107, "Shamisen", 0 }, +{ 108, "Koto", 0 }, +{ 109, "Kalimba", 0 }, +{ 110, "Bag pipe", 0 }, +{ 111, "Fiddle", 0 }, +{ 112, "Shanai", 0 }, +{ 113, "Tinkle Bell", 0 }, +{ 114, "Agogo", 0 }, +{ 115, "Steel Drums", 0 }, +{ 116, "Woodblock", 0 }, +{ 117, "Taiko Drum", 0 }, +{ 118, "Melodic Tom", 0 }, +{ 119, "Synth Drum", 0 }, +{ 120, "Reverse Cymbal", 0 }, +{ 121, "Guitar Fret Noise", 0 }, +{ 122, "Breath Noise", 0 }, +{ 123, "Seashore", 0 }, +{ 124, "Bird Tweet", 0 }, +{ 125, "Telephone Ring", 0 }, +{ 126, "Helicopter", 0 }, +{ 127, "Applause", 0 }, +{ 128, "Gunshot", 0 } }; + +struct GM_perc { + int midi_nr; + const char *name; + int col; +}; + +GM_perc gm_perc[perc_hi-perc_lo+1]= { + // upto 34 and from 82: from timidity patches, /etc/timidity.cfg +{ perc_lo /*27*/, "Highq", 2 }, +{ 28, "Slap", 2 }, +{ 29, "Scratch1", 3 }, +{ 30, "Scratch2", 3 }, +{ 31, "Sticks", 3 }, +{ 32, "Sqrclick", 5 }, +{ 33, "Metclick", 5 }, +{ 34, "Metbell", 5 }, +{ 35, "Acoustic Bass Drum", 0 }, +{ 36, "Bass Drum 1", 0 }, +{ 37, "Side Stick", 1 }, +{ 38, "Acoustic Snare", 1 }, +{ 39, "Hand Clap", 4 }, +{ 40, "Electric Snare", 2 }, +{ 41, "Low Floor Tom", 0 }, +{ 42, "Closed Hi Hat", 3 }, +{ 43, "High Floor Tom", 4 }, +{ 44, "Pedal Hi-Hat", 5 }, +{ 45, "Low Tom", 0 }, +{ 46, "Open Hi-Hat", 3 }, +{ 47, "Low-Mid Tom", 1 }, +{ 48, "Hi-Mid Tom", 2 }, +{ 49, "Crash Cymbal 1", 3 }, +{ 50, "High Tom", 3 }, +{ 51, "Ride Cymbal 1", 3 }, +{ 52, "Chinese Cymbal", 2 }, +{ 53, "Ride Bell", 5 }, +{ 54, "Tambourine", 4 }, +{ 55, "Splash Cymbal", 3 }, +{ 56, "Cowbell", 3 }, +{ 57, "Crash Cymbal 2", 3 }, +{ 58, "Vibraslap", 3 }, +{ 59, "Ride Cymbal 2", 2 }, +{ 60, "Hi Bongo", 4 }, +{ 61, "Low Bongo", 1 }, +{ 62, "Mute Hi Conga", 4 }, +{ 63, "Open Hi Conga", 4 }, +{ 64, "Low Conga", 1 }, +{ 65, "High Timbale", 5 }, +{ 66, "Low Timbale", 1 }, +{ 67, "High Agogo", 5 }, +{ 68, "Low Agogo", 1 }, +{ 69, "Cabasa", 2 }, +{ 70, "Maracas", 2 }, +{ 71, "Short Whistle", 5 }, +{ 72, "Long Whistle", 4 }, +{ 73, "Short Guiro", 4 }, +{ 74, "Long Guiro", 3 }, +{ 75, "Claves", 5 }, +{ 76, "Hi Wood Block", 5 }, +{ 77, "Low Wood Block", 1 }, +{ 78, "Mute Cuica", 2 }, +{ 79, "Open Cuica", 2 }, +{ 80, "Mute Triangle", 4 }, +{ 81, "Open Triangle", 4 }, +{ 82, "Shaker",3 }, +{ 83, "Jingles",4 }, +{ 84, "Bell tree",2 }, +{ 85, "Castinet",3 }, +{ 86, "Surdo1",1 }, +{ perc_hi /*87*/, "Surdo2",1 } }; + +struct ProgChange { + int time, + gm_nr, + group_nr, + transp, + col_nr; + char *tune; + ProgChange(int t,int gmnr):time(t),gm_nr(gmnr),group_nr(0),transp(0),col_nr(0),tune(0) { } + bool operator==(ProgChange &pc) { return time==pc.time; } + bool operator<(ProgChange &pc) { return time pc_list; + SLList_elem *cur_pc; + void reset() { pc_list.reset(); cur_pc=0; } +} channels[16]; + +static FILE *midi_f; + +static bool tinc_is_one=true; // t_incr = 1.0? +const int msgbuf_max=1000; +static int Mf_toberead, + Mf_bytesread, + Mf_currtime, + Msgindex, + ntrks, + division=1, + nu_pq=4; // amuc note units per quarter +bool alert_mes; +static char Msgbuff[msgbuf_max]; +static float t_incr=1.; + +struct Midi_Note { + uchar lnr,sign, + occ; // occupied? + bool valid; // not out of range? + int start_time; + ProgChange *pch_data; +} note_arr[128][16]; + +struct MidiPercNote { + uchar occ; // occupied? +} perc_note_arr[perc_hi+1]; + +MidiIn midi_in; + +static int divide(int a,int b) { return (2 * a + b)/b/2; } + +int get_perc_instr(int midi_nr) { + if (midi_nrperc_hi) { + alert_mes=true; + return 5; + } + return gm_perc[midi_nr-perc_lo].col; +} + +int egetc() { + int c = getc(midi_f); + + if (c == EOF) { alert("unexpected end-of-file"); return c; } + Mf_toberead--; + Mf_bytesread++; + return c; +} + +int readvarinum() +{ + int value; + int c; + + c = egetc(); + value = c; + if ( c & 0x80 ) { + value &= 0x7f; + do { + c = egetc(); + value = (value << 7) + (c & 0x7f); + } while (c & 0x80); + } + return value; +} + +int to32bit(int c1,int c2,int c3,int c4) { + int value = 0; + + value = (c1 & 0xff); + value = (value<<8) + (c2 & 0xff); + value = (value<<8) + (c3 & 0xff); + value = (value<<8) + (c4 & 0xff); + return value; +} + +int to16bit(int c1, int c2) { + return ((c1 & 0xff ) << 8) + (c2 & 0xff); +} + +int read32bit() { + int c1, c2, c3, c4; + + c1 = egetc(); + c2 = egetc(); + c3 = egetc(); + c4 = egetc(); + return to32bit(c1,c2,c3,c4); +} + +int read16bit() { + int c1, c2; + c1 = egetc(); + c2 = egetc(); + return to16bit(c1,c2); +} + +MidiIn::MidiIn() { +} + +struct MidiScores { + static const int sc_max=40; + Score *buf[sc_max]; + void reset() { + for (int i=0;isigns_mode=midi_in.acc; + buf[i]->tone2signs(midi_in.key_nr % keys_max); + buf[i]->ngroup=-1; + return buf[i]; + } + if (!strcmp(ind2tname(buf[i]->name),name)) return buf[i]; + } + alert ("get_score: more then %d scores",sc_max); + return 0; + } +} midi_scores; + +void midi_note_on(int chan,int time,int midi_nr,ProgChange& pch) { + uchar lnr,sign; + Midi_Note *nb=note_arr[midi_nr]+chan; + ++nb->occ; + if (nb->occ==1) { // else no opdating + nb->start_time=time; + if (midinr_to_lnr(midi_nr+midi_in.shift+pch.transp,lnr,sign,midi_in.acc)) { + nb->valid=true; + nb->lnr=lnr; + nb->sign=sign; + nb->pch_data=&pch; + } + else nb->valid=false; + } +} + +void midi_note_off(int chan,int time,int midi_nr) { + Midi_Note *nb=note_arr[midi_nr]+chan; + if (nb->occ) { + --nb->occ; + //if (nb->occ) alert("unexpected note %d, channel %d, time %d",midi_nr,chan+1,time*4/division); + if (nb->valid && !nb->occ) { // else do nothing + Score *sc=midi_scores.get_score(nb->pch_data->tune); + if (sc) { + if (sc->ngroup>=0 && sc->ngroup!=nb->pch_data->group_nr) + alert("group nr mismatch in .gm-map file (tune: '%s')",ind2tname(sc->name)); + else + sc->ngroup=nb->pch_data->group_nr; + sc->insert_midi_note(nb->start_time,time,nb->lnr,nb->pch_data->col_nr,nb->sign); + } + } + } + else + alert("note %d lost, channel %d, time %d",midi_nr,chan+1,time*4/division); +} + +void midi_perc_on(int chan,int time,int midi_nr,ProgChange& pch) { // midi_nr: 35 - 81 + MidiPercNote *nb=perc_note_arr+midi_nr; + ++nb->occ; + int instr=get_perc_instr(midi_nr); + Score *sc=midi_scores.get_score(pch.tune); + if (sc) { + sc->ngroup=pch.group_nr; + sc->insert_midi_perc(time,sclin_max-1-instr,instr); + } +} + +void midi_perc_off(int time,int midi_nr) { + if (midi_nrperc_hi) + return; + MidiPercNote *nb=perc_note_arr+midi_nr; + if (nb->occ) + --nb->occ; + else { + alert("percussion note %d lost, time %d",midi_nr,time*4/division); + } +} + +void midi_noteOn(int chan,int time,int midi_nr,ProgChange& pch) { + //if (debug) printf("noteOn: chan=%d time=%d instr=%d nr=%d transp=%d\n",chan,time,instr,midi_nr,transp); + if (chan==9) // percussion + midi_perc_on(chan,time,midi_nr,pch); + else + midi_note_on(chan,time,midi_nr,pch); +} + +void midi_noteOff(int chan,int time,int midi_nr,ProgChange& pch) { + //if (debug) printf("noteOff: chan=%d time=%d instr=%d nr=%d\n",chan,time,instr,midi_nr); + if (chan==9) // percussion + midi_perc_off(time,midi_nr); + else + midi_note_off(chan,time,midi_nr); +} + +int cur_time() { // rounded to nearest integer + if (tinc_is_one) + return divide(subdiv*Mf_currtime*nu_pq,division); + return lrint(subdiv*Mf_currtime*nu_pq*t_incr/division); +} + +bool fill_chan_array(FILE *gmmap) { + int chan_nr, + time; + Str str; + for (;;) { + str.rword(gmmap,"="); // "channel=" + if (str.ch==EOF) break; + if (str!="channel") { alert(".gm-map file: missing 'channel' item"); return false; } + str.rword(gmmap," "); + chan_nr=atoi(str.s)-1; + if (chan_nr<0 || chan_nr>=16) { alert(".gm-map file: channel nr out of range"); return false; } + str.rword(gmmap,"="); // "time=" + str.rword(gmmap," \""); + time=atoi(str.s); + if (debug) printf("fill_chan_arr: time=%d cnr=%d\n",time,chan_nr); + SLList_elem *pc=channels[chan_nr].pc_list.insert(ProgChange(time,chan_nr),true); + if (str.ch=='"') + str.rword(gmmap,"\""); + else + str.rword(gmmap," "); + if (chan_nr!=9) { + str.rword(gmmap," "); // patch nr + str.rword(gmmap," "); // color + pc->d.col_nr=color_nr(str.s,0); + } + str.rword(gmmap," "); // tune name + pc->d.tune=strdup(str.s); + str.rword(gmmap," \n"); + pc->d.group_nr=atoi(str.s); + if (chan_nr==9) { // percussion? + if (str.ch!='\n') { + alert("wrong nr items in .gm-map file (expected 5)"); + return false; + } + } + else { + if (str.ch==' ') { + str.rword(gmmap," \n"); + pc->d.transp=atoi(str.s); + } + if (str.ch!='\n') { + alert("wrong nr items in .gm-map file (expected 7 or 8)"); + return false; + } + } + } + return true; +} + +void print_mapf(FILE *gmmap) { + int chan; + SLList_elem *pc; + for (chan=0;chan<16;++chan) { + if (chan==9) { + fprintf(gmmap,"channel=10 time=0 %-30s midi 0\n","PERCUSSION"); + } + else { + char name[40]; + for (pc=channels[chan].pc_list.lis;pc;pc=pc->nxt) { + snprintf(name,40,"\"%s\" (%d)",gm_instr[pc->d.gm_nr].name,gm_instr[pc->d.gm_nr].gm_nr); + fprintf(gmmap,"channel=%-2d time=%-3d %-30s black midi 0\n",chan+1,pc->d.time,name); + } + } + } +} + +bool MidiIn::chanmessage(int status,int c1,int c2) { + int chan = status & 0xf; + status = status & 0xf0; + switch (status) { + case 0x80: + case 0x90: + if (read_mapf) { + MidiChannel *mc=channels+chan; + if (status==0x90) { + if (!mc->cur_pc) { alert(".gm-map file: unexpected channel nr %d",chan+1); return false; } + while (mc->cur_pc->nxt && mc->cur_pc->nxt->d.timecur_pc=mc->cur_pc->nxt; + } + SLList_elem *pc=mc->cur_pc; + if (debug) printf("chanmessage: stat=0x%x pc=%p\n",status,pc); + if (status==0x90 && c2>0) + midi_noteOn(chan,cur_time(),c1,pc->d); + else + midi_noteOff(chan,cur_time(),c1,pc->d); + } + break; + case 0xa0: + if (debug) puts("Mf_pressure"); + break; + case 0xb0: + if (debug) printf("Mf_parameter: %d %d %d\n",chan,c1,c2); + break; + case 0xe0: + if (debug) puts("Mf_pitchbend"); + break; + case 0xc0: + if (debug) printf("Mf_program: chan=%d c1=%d\n",chan,c1); + if (!read_mapf) + channels[chan].pc_list.insert(ProgChange(Mf_currtime,c1),true); + break; + case 0xd0: + if (debug) puts("Mf_chanpressure"); + break; + default: + printf("chanmessage: %x\n",status); + } + return true; +} + +void msgadd(int c) { + if (Msgindex >= msgbuf_max-1) { + alert("msgadd: buf overflow"); + } + else { + Msgbuff[Msgindex++] = c; + Msgbuff[Msgindex] = 0; + } +} + +void metaevent(int type) { + int leng = Msgindex; + char *m = Msgbuff; + + switch ( type ) { + case 0x00: + if (debug) printf("Mf_seqnum: %d\n",to16bit(m[0],m[1])); + break; + case 0x01: /* Text event */ + case 0x02: /* Copyright notice */ + case 0x03: /* Sequence/Track name */ + case 0x04: /* Instrument name */ + case 0x05: /* Lyric */ + case 0x06: /* Marker */ + case 0x07: /* Cue point */ + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + /* These are all text events */ + if (debug) printf("Mf_text: %d %d '%s'\n",type,leng,m); + break; + case 0x2f: /* End of Track */ + if (debug) puts("Mf_eot"); + break; + case 0x51: /* Set tempo */ + if (debug) printf("Mf_tempo: %d %d %d\n",m[0],m[1],m[2]); + break; + case 0x54: + if (debug) printf("Mf_smpte: %d %d %d %d %d\n",m[0],m[1],m[2],m[3],m[4]); + break; + case 0x58: + nu_pq=m[3]/2; + if (debug) printf("Mf_timesig: %d %d %d %d\n",m[0],m[1],m[2],m[3]); + break; + case 0x59: + if (debug) printf("Mf_keysig: %d %d\n",m[0],m[1]); + break; + case 0x7f: + if (debug) printf("Mf_seqspecific %d '%s'\n",leng,m); + break; + default: + if (debug) printf("Mf_metamisc: %d %d '%s'\n",type,leng,m); + } +} + +void sysex() +{ + if (debug) printf("Mf_sysex: %d %s\n",Msgindex,Msgbuff); +} + +bool MidiIn::readtrack() { /* read a track chunk */ + /* This array is indexed by the high half of a status byte. It's */ + /* value is either the number of bytes needed (1 or 2) for a channel */ + /* message, or 0 (meaning it's not a channel message). */ + static int chantype[] = { + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */ + 2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */ + }; + char buf[10]; + int lookfor, + c, c1, type, + status = 0, /* status value (e.g. 0x90==note-on) */ + needed, + varinum; + bool sysexcontinue = false, /* 1 if last message was an unfinished sysex */ + running = false; /* 1 when running status used */ + + if (fread(buf,4,1,midi_f)!=1) { alert("readtrack: fread problem"); return false; } + if (strncmp(buf,"MTrk",4)) { + alert("track start 'MTrk' not found"); + return false; + } + + Mf_toberead = read32bit(); + Mf_currtime = 0; + Mf_bytesread =0; + for (int i=0;i<16;++i) channels[i].cur_pc=channels[i].pc_list.lis; + + if (debug) printf("Mf_trackstart, %d bytes\n",Mf_toberead); + + while ( Mf_toberead > 0 ) { + + Mf_currtime += readvarinum(); /* delta time */ + + c = egetc(); + + if ( sysexcontinue && c != 0xf7 ) { + alert("didn't find expected continuation of a sysex"); + return false; + } + if ( (c & 0x80) == 0 ) { /* running status? */ + if ( status == 0 ) { + alert("unexpected running status"); + return false; + } + running = true; + } + else { + status = c; + running = false; + } + + needed = chantype[ (status>>4) & 0xf ]; + + if ( needed ) { /* ie. is it a channel message? */ + if ( running ) + c1 = c; + else + c1 = egetc(); + if (!chanmessage( status, c1, (needed>1) ? egetc() : 0 )) { + return false; + } + continue; + } + + switch ( c ) { + case 0xff: /* meta event */ + type = egetc(); + if (debug) printf("Meta event, type=0x%x\n",type); + varinum = readvarinum(); + lookfor = Mf_toberead - varinum; + Msgindex = 0; + + while ( Mf_toberead > lookfor ) + msgadd(egetc()); + + metaevent(type); + break; + + case 0xf0: /* start of system exclusive */ + if (debug) printf("Start sysex\n"); + varinum = readvarinum(); + lookfor = Mf_toberead - varinum; + Msgindex = 0; + msgadd(0xf0); + + while ( Mf_toberead > lookfor ) + msgadd(c=egetc()); + + if ( c==0xf7 ) + sysex(); + else + sysexcontinue = true; /* merge into next msg */ + break; + + case 0xf7: /* sysex continuation or arbitrary stuff */ + if (debug) printf("Sysex continuation\n"); + + varinum = readvarinum(); + lookfor = Mf_toberead - varinum; + + if ( ! sysexcontinue ) + Msgindex = 0; + + while ( Mf_toberead > lookfor ) + msgadd(c=egetc()); + + if (!sysexcontinue) { + if (debug) printf("Mf_arbitrary: %d %s\n",Msgindex,Msgbuff); + } + else if ( c == 0xf7 ) { + sysex(); + sysexcontinue = 0; + } + break; + default: + alert("track: unexpected byte %x",c); + if (debug) printf("UNEXPECTED BYTE %x\n",c); + break; + } + } + if (debug) puts("end of track"); + return true; +} + +bool MidiIn::read_mf(const char* midi_fn,const char* i_map_fn) { + static char buf[10]; + int i,i2, + format, + track; + Str str; + shift=0; format=0; key_nr=0; acc=eLo; + alert_mes=false; + for (i=0;i<16;++i) channels[i].reset(); + if (read_mapf) { + FILE *gm_map=fopen(i_map_fn,"r"); + if (!gm_map) { + alert("%s not opened",i_map_fn); + return false; + } + str.rword(gm_map," \n"); + if (str=="set") { + for (;;) { + str.rword(gm_map,":\n"); + if (str=="format") { + str.rword(gm_map," \n"); + format=atoi(str.s); + if (format!=2) { + alert(".gm-map file: wrong format (expected: 2). Delete or rename it."); + return false; + } + } + else if (str=="key") { + str.rword(gm_map," \n"); + if (!key_name_to_keynr(str.s,key_nr)) return false; + } + else if (str=="acc") { + str.rword(gm_map," \n"); + if (str=="sharp") acc=eHi; + else if (str=="flat") acc=eLo; + else { + alert("unknown acc '%s' (should be 'sharp' or 'flat') in %s",str.s,i_map_fn); + return false; + } + } + else if (str=="shift") { + str.rword(gm_map," \n"); + shift=atoi(str.s); + } + else if (str=="tinc") { + str.rword(gm_map," \n"); + t_incr=atof(str.s); + if (fabs(t_incr-1.0)>0.01) { + tinc_is_one=false; + app->act_meter=lrint(8*t_incr); + } + } + else { + alert("unknown parameter '%s' in %s",str.s,i_map_fn); + return false; + } + if (str.ch=='\n') + break; + } + } + else { + alert("no first 'set ...' line in %s",i_map_fn); + return false; + } + if (!fill_chan_array(gm_map)) return false; + fclose(gm_map); + } + if ((midi_f=fopen(midi_fn,"r"))==0) { + alert("midi file '%s' not found",midi_fn); + return false; + } + for (i=0;i<128;++i) { + for (i2=0;i2<16;++i2) note_arr[i][i2].occ=0; + } + for (i=0;i<=perc_hi;++i) + perc_note_arr[i].occ=0; + midi_scores.reset(); + if (fread(buf,4,1,midi_f)!=1) { alert("fread problem, file %s",midi_fn); return false; } + if (strncmp(buf,"MThd",4)) { + alert("unexpected start '%s' in %s (should be 'MThd')",buf,midi_fn); + fclose(midi_f); + return false; + } + Mf_toberead = read32bit(); + Mf_bytesread = 0; + format = read16bit(); + ntrks = read16bit(); + division = read16bit(); + if (debug) printf("format=%d ntrks=%d division=%d\n",format,ntrks,division); + + while (Mf_toberead > 0) // flush any extra stuff + egetc(); + track=1; + bool init_ok=true; + while ((init_ok=readtrack())==true) { + if (++track>ntrks) break; + } + fclose(midi_f); + if (debug) printf("read_mf: read_mapf=%d\n",read_mapf); + if (!read_mapf) { + FILE *gm_map=fopen(i_map_fn,"w"); + if (!gm_map) { + alert("%s not opened",i_map_fn); + return false; + } + fputs("set format:2 key:C acc:flat\n",gm_map); + print_mapf(gm_map); + fclose(gm_map); + } + if (!init_ok) return false; + if (alert_mes) + alert("warning: out-of-range percussion instruments"); + return true; +} diff --git a/src/midi-in.h b/src/midi-in.h new file mode 100644 index 0000000..9d2ba36 --- /dev/null +++ b/src/midi-in.h @@ -0,0 +1,12 @@ +struct MidiIn { + MidiIn(); + bool read_mapf; + int key_nr, + acc, // accidentals: eHi, eLo + shift; // increase midi nr + bool read_mf(const char *midi_fn,const char* i_map_fn); + bool chanmessage(int status,int c1,int c2); + bool readtrack(); +}; + +extern MidiIn midi_in; diff --git a/src/midi-keyb-jack.cpp b/src/midi-keyb-jack.cpp new file mode 100644 index 0000000..6859750 --- /dev/null +++ b/src/midi-keyb-jack.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include "midi-keyb.h" + +typedef unsigned char uchar; + +snd_midi_event_t *alsa_decoder; + +void midi_action(snd_seq_t *seq_handle) { + snd_seq_event_t *ev; + static uchar buffer[16]; + int count, + nbytes=0; + do { + snd_seq_event_input(seq_handle, &ev); + count = snd_midi_event_decode(alsa_decoder, buffer, 16, ev); + if (count > 0 && count < 16) { + nbytes = count; + count++; + } + snd_seq_free_event(ev); + } while (snd_seq_event_input_pending(seq_handle, 0) > 0); + uchar code=buffer[0] & 0xf0; + if (midi_mes) printf("nbytes=%d buffer=[%x,%x,%x], code=%x\n",nbytes,buffer[0],buffer[1],buffer[2],code); + switch (code) { + case 0x80: + if (mk_connected) + keyb_noteOff(buffer[1]); + break; + case 0x90: + if (mk_connected) { + if (buffer[2]>0) keyb_noteOn(buffer[1]); + else keyb_noteOff(buffer[1]); + } + break; + case 0xc0: { // change instrument + int instr=buffer[1]+1; + if (midi_mes) printf(" instr: %d\n",instr); + } + break; + case 0xb0: // set volume or modulation + if (midi_mes) { + if (buffer[1]==0x07) printf(" volume: %d\n",buffer[2]); + else printf(" modulation: %d\n",buffer[2]); + } + break; + default: + if (midi_mes) putchar('\n'); + } +} + +void *conn_mk_jack(void*) { + mk_connected=false; + int portid; + int npfd; + struct pollfd *pfd; + snd_seq_t *seq_handle; + + if (snd_seq_open(&seq_handle, "hw", SND_SEQ_OPEN_INPUT, 0) < 0) { + alert("Error opening ALSA sequencer"); + return 0; + } + snd_seq_set_client_name(seq_handle, "Amuc midi keyboard"); + if ((portid = snd_seq_create_simple_port(seq_handle, "midi_in", + SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, + SND_SEQ_PORT_TYPE_APPLICATION)) < 0) { + alert("Error creating sequencer port"); + return 0; + } + npfd = snd_seq_poll_descriptors_count(seq_handle, POLLIN); + pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd)); + snd_seq_poll_descriptors(seq_handle, pfd, npfd, POLLIN); + + if (snd_midi_event_new(16, &alsa_decoder)) { + alert("Error initializing ALSA MIDI decoder"); + return 0; + } + snd_midi_event_reset_decode(alsa_decoder); + snd_midi_event_no_status(alsa_decoder, 1); + if (midi_mes) puts("start polling"); + mk_connected=true; + while (mk_connected) { + if (poll(pfd, npfd, 1000) > 0) + midi_action(seq_handle); + } + snd_seq_close(seq_handle); + stop_conn_mk(); + return 0; +} diff --git a/src/midi-keyb.cpp b/src/midi-keyb.cpp new file mode 100644 index 0000000..742d825 --- /dev/null +++ b/src/midi-keyb.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include "midi-keyb.h" + +typedef unsigned char uchar; + +static const int BUFFER_SIZE=1024; + +enum parserState { + stIgnore, dtaIgnore, + stNoteOff, dtaNoteOff1, dtaNoteOff2, + stNoteOn, dtaNoteOn1, dtaNoteOn2, + stControlChg, dtaControlChg1, dtaControlChg2, + stProgramChg, dtaProgramChg +}; + +struct statusTableRow { + int state; +} statusTable[16]; + +bool midi_mes=false; +uchar inBuffer[BUFFER_SIZE]; +int midi_fd; +const char *midi_input_dev="/dev/midi1"; + +static void loadStatusTable () { + for (int i=0; i < 16; i++) + statusTable[i].state = stIgnore; + statusTable[0x80/0x10].state = stNoteOff; + statusTable[0x90/0x10].state = stNoteOn; + statusTable[0xb0/0x10].state = stControlChg; + statusTable[0xc0/0x10].state = stProgramChg; +} + +void run_keyboard() { + int inBytes, + state = stIgnore; + uchar byte1=0,byte2=0, + *mbp, + *mbpEnd; + while (mk_connected) { + if ((inBytes = read(midi_fd,(void *)inBuffer,BUFFER_SIZE)) < 0) { + alert("read midi_fd failed"); + break; + } + if (inBytes == 0) continue; + mbp = inBuffer; + mbpEnd = mbp + inBytes; + + while (mbp < mbpEnd && mk_connected) { + uchar B = * mbp++; /* Get next MIDI byte from buffer */ + if (0xF8 & B == 0xF8) + continue; /* Ignore */ + if (B & 0x80) { + byte1 = B; + state = statusTable[B/0x10].state; + } + if (midi_mes) printf("\tB=0x%x state=%d\n",B,state); + switch (state) { + case stNoteOff: + state = dtaNoteOff1; + break; + case dtaNoteOff1: + byte2 = B; /* Note number */ + state = dtaNoteOff2; + break; + case dtaNoteOff2: + if (midi_mes) printf("\t off: %d\n",byte2); /* B is release velocity */ + if (mk_connected) keyb_noteOff(byte2); + state = dtaNoteOff1; + break; + case stNoteOn: + state = dtaNoteOn1; + break; + case dtaNoteOn1: + byte2 = B; /* Note number */ + state = dtaNoteOn2; + break; + case dtaNoteOn2: + if (B>0) { /* B is note on velocity */ + if (midi_mes) printf("\ton: %d %d\n",byte2,B); + if (mk_connected) keyb_noteOn(byte2); + } else { + if (midi_mes) printf("\toff: %d %d\n",byte2,B); /* Zero velocity on = off */ + if (mk_connected) keyb_noteOff(byte2); + } + state = dtaNoteOn1; + break; + case stControlChg: + state = dtaControlChg1; + break; + case dtaControlChg1: + byte2 = B; /* Controller number */ + state = dtaControlChg2; + break; + case dtaControlChg2: + if (midi_mes) printf("\tctrl data=%d\n",B); /* B is controller data */ + state = dtaControlChg1; + break; + case stProgramChg: + state = dtaProgramChg; + break; + case dtaProgramChg: + if (midi_mes) printf("\nprog change, prog=%d\n",B); /* B is program number */ + state = dtaProgramChg; + break; + case stIgnore: + state = dtaIgnore; + break; + case dtaIgnore: + break; + default: + printf("midi-keyb: unexpected state=0x%x\n",state); + } + } + } +} + +void *conn_mk_alsa(void*) { + mk_connected=false; + if ((midi_fd = open (midi_input_dev, O_RDONLY, 0)) == -1) { + alert("open %s failed",midi_input_dev); + alert("If midi keyboard connected correctly,"); + alert(" then check 'midi_input' in .amucrc file"); + } + else { + loadStatusTable(); + mk_connected=true; + run_keyboard(); + close(midi_fd); + } + mk_connected=false; + stop_conn_mk(); + return 0; +} diff --git a/src/midi-keyb.h b/src/midi-keyb.h new file mode 100644 index 0000000..f079dca --- /dev/null +++ b/src/midi-keyb.h @@ -0,0 +1,13 @@ +void *conn_mk_alsa(void*); // in midi-keyb.cpp +void *conn_mk_jack(void*); // in midi-keyb-jack.cpp +void stop_conn_mk(); // in amuc.cpp +void keyb_noteOn(int midi_nr); // in sound.cpp +void keyb_noteOff(int midi_nr); + +void alert(const char *form,...); +void say(const char *form,...); + +extern bool debug, + mk_connected, + midi_mes; +extern const char *midi_input_dev; diff --git a/src/midi-out-1tr.h b/src/midi-out-1tr.h new file mode 100644 index 0000000..03740ee --- /dev/null +++ b/src/midi-out-1tr.h @@ -0,0 +1,20 @@ +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; + +struct MidiOut { + int sub_div; // from sound.h + int col2midi_instr[groupnr_max][colors_max]; // instruments + int col2smp_instr[groupnr_max][colors_max]; // percussion, channel 10 + int col2pan[colors_max]; + int instr_mapping[groupnr_max][colors_max]; // actual mapping + MidiOut(); + bool init(const char*,int subd,int us_per_beat,int meter,int nu_per_quarter); + void close(); + void note_onoff(uchar tag,int col,int grn,int midi_chan,bool sampled,int time,int nr,int ampl); +}; + +extern MidiOut midi_out; +extern bool debug; + +void alert(char *form,...); diff --git a/src/midi-out.cpp b/src/midi-out.cpp new file mode 100644 index 0000000..7d6d46b --- /dev/null +++ b/src/midi-out.cpp @@ -0,0 +1,413 @@ +/* Generated tags: 0x80, + 0xb0 ... 0xb5, 0, + 0x40, + 0xc0 ... 0xc5, 0 ... 17, + 0xc9, 0, + 0x7f, + 0xff, + 0x2f, 0 + For the Timidity midi-player the events must be in a certain order - found out by trial-and-error. +*/ +#include +#include +#include "colors.h" +#include "midi-out.h" + +static const char* gm_instr[128]= { + "Acoustic Grand Piano", + "Bright Acoustic Piano", + "Electric Grand Piano", + "Honky-tonk Piano", + "Electric Piano 1", + "Electric Piano 2", + "Harpsichord", + "Clavi", + "Celesta", + "Glockenspiel" , + "Music Box" , + "Vibraphone" , + "Marimba" , + "Xylophone" , + "Tubular Bells" , + "Dulcimer" , + "Drawbar Organ" , + "Percussive Organ" , + "Rock Organ" , + "Church Organ" , + "Reed Organ" , + "Accordion" , + "Harmonica" , + "Tango Accordion" , + "Acoustic Guitar (nylon)" , + "Acoustic Guitar (steel)" , + "Electric Guitar (jazz)" , + "Electric Guitar (clean)" , + "Electric Guitar (muted)" , + "Overdriven Guitar" , + "Distortion Guitar" , + "Guitar harmonics" , + "Acoustic Bass" , + "Electric Bass (finger)" , + "Electric Bass (pick)" , + "Fretless Bass" , + "Slap Bass 1" , + "Slap Bass 2" , + "Synth Bass 1" , + "Synth Bass 2" , + "Violin" , + "Viola" , + "Cello" , + "Contrabass" , + "Tremolo Strings" , + "Pizzicato Strings" , + "Orchestral Harp" , + "Timpani" , + "String Ensemble 1" , + "String Ensemble 2" , + "SynthStrings 1" , + "SynthStrings 2" , + "Choir Aahs" , + "Voice Oohs" , + "Synth Voice" , + "Orchestra Hit" , + "Trumpet" , + "Trombone" , + "Tuba" , + "Muted Trumpet" , + "French Horn" , + "Brass Section" , + "SynthBrass 1" , + "SynthBrass 2" , + "Soprano Sax" , + "Alto Sax" , + "Tenor Sax" , + "Baritone Sax" , + "Oboe" , + "English Horn" , + "Bassoon" , + "Clarinet" , + "Piccolo" , + "Flute" , + "Recorder" , + "Pan Flute" , + "Blown Bottle" , + "Shakuhachi" , + "Whistle" , + "Ocarina" , + "Lead 1 (square)" , + "Lead 2 (sawtooth)" , + "Lead 3 (calliope)" , + "Lead 4 (chiff)" , + "Lead 5 (charang)" , + "Lead 6 (voice)" , + "Lead 7 (fifths)" , + "Lead 8 (bass + lead)" , + "Pad 1 (new age)" , + "Pad 2 (warm)" , + "Pad 3 (polysynth)" , + "Pad 4 (choir)" , + "Pad 5 (bowed)" , + "Pad 6 (metallic)" , + "Pad 7 (halo)" , + "Pad 8 (sweep)" , + "FX 1 (rain)" , + "FX 2 (soundtrack)" , + "FX 3 (crystal)" , + "FX 4 (atmosphere)", + "FX 5 (brightness)", + "FX 7 (echoes)", + "FX 8 (sci-fi)", + "Sitar", + "Banjo", + "Shamisen", + "Koto", + "Kalimba", + "Bag pipe", + "Fiddle", + "Shanai", + "Tinkle Bell", + "Agogo", + "Steel Drums", + "Woodblock", + "Taiko Drum", + "Melodic Tom", + "Synth Drum", + "Reverse Cymbal", + "Guitar Fret Noise", + "Breath Noise", + "Seashore", + "Bird Tweet", + "Telephone Ring", + "Helicopter", + "Applause", + "Gunshot" }; + +MidiOut midi_out; + +static uchar *reord16(uint n) { + static uchar buf[2]; + buf[0]=(n>>8) & 0xff; + buf[1]=n & 0xff; + return buf; +} + +static uchar *reord8(uchar n) { + static uchar buf[1]; + buf[0]=n; + return buf; +} + +static uchar *reord24(uint n) { + static uchar buf[3]; + buf[0]=(n>>16) & 0xff; + buf[1]=(n>>8) & 0xff; + buf[2]=n & 0xff; + return buf; +} + +static uchar *reord32(uint n) { + static uchar buf[4]; + buf[0]=(n>>24) & 0xff; + buf[1]=(n>>16) & 0xff; + buf[2]=(n>>8) & 0xff; + buf[3]=n & 0xff; + return buf; +} + +void check(bool ok) { if (!ok) puts("fwrite problem"); } + +static void wr(uchar *n,int nr) { + midi_out.data_size+=nr; + check(1==fwrite(n, nr,1,midi_out.midi_f)); +} + +static void del_0() { + uchar i8=0; + wr(&i8, 1); +} + +static void var_int(uint val) { + uchar bytes[5]; + uchar i8; + bytes[0] = (val >> 28) & 0x7f; // most significant 5 bits + bytes[1] = (val >> 21) & 0x7f; // next largest 7 bits + bytes[2] = (val >> 14) & 0x7f; + bytes[3] = (val >> 7) & 0x7f; + bytes[4] = (val) & 0x7f; // least significant 7 bits + + int start = 0; + while (start<5 && bytes[start] == 0) start++; + + for (int i=start; i<4; i++) { + i8=bytes[i] | 0x80; + wr(&i8, 1); + } + i8=bytes[4]; + wr(&i8, 1); +} + +MidiOut::MidiOut() { +} + +bool MidiOut::init(const char *fn,int subd,int nupq,int met,int us_pb) { // sets init_ok if succesful + init_ok=false; + sub_div=subd; + nu_pq=nupq; + meter=met; + us_per_beat=us_pb; + if ((midi_f=fopen(fn,"w"))==0) { + alert("%s not opened",fn); + return false; + } + for (int grn=0;grn=100) { alert("midi: bad note"); return 60; } + return n; +} + +int MidiOut::get_chan(bool sampled,int ind) { // ind = index in col_gr2chan[] + if (sampled) return 9; + if (col_gr2chan[ind]>=0) return col_gr2chan[ind]; + if (new_chan==16-1) { + if (!chan_warn) { chan_warn=true; alert("midi: channels > 16"); } + return 0; + } + ++new_chan; + if (new_chan==9) ++new_chan; // skip percussion channel + return (col_gr2chan[ind]=new_chan); +} + +void MidiOut::note_onoff(bool start,int col,int grn,int midi_instr,bool sampled,int tim,int nr,int ampl) { + if (!init_ok) return; + if (debug) printf("start=%d col=%d grn=%d sampled=%d tim=%d instr=%d midi-nr=%d ampl=%d\n", + start,col,grn,sampled,tim,midi_instr,nr,ampl); + int chan=get_chan(sampled,col+grn*colors_max); + if (!sampled && instr_mapping[grn][col]!=midi_instr) { + if (debug) printf("instr from %d to %d, chan=%d\n",instr_mapping[grn][col],midi_instr,chan); + instr_mapping[grn][col]=midi_instr; + + del_0(); + wr(reord8(0xff),1); // meta event + wr(reord8(0x03),1); // track title + char title[100]; + int len=snprintf(title,100,"track %d - %s",cur_track,gm_instr[midi_instr]); + var_int(len); + wr((uchar*)title,len); + del_0(); + wr(reord8(0xb0 + chan),1); // control change mode + + wr(reord8(0x40),1); // damper on/off + wr(reord8(0),1); // damped + del_0(); + wr(reord8(0x7),1); // volume + wr(reord8(80),1); // good balance with percussion amplitude + + // following events will be timed! + if (tim==cur_time) del_0(); + else { + var_int((tim-cur_time)*sub_div*4/nu_pq); + cur_time=tim; + } + wr(reord8(0x0a),1); // pan + wr(reord8(col2pan[col]),1); + del_0(); + wr(reord8(0xc0 + chan),1); // program change + wr(reord8(midi_instr),1); // instrument + del_0(); + } + else { + if (tim==cur_time) del_0(); + else { + var_int((tim-cur_time)*sub_div*4/nu_pq); + cur_time=tim; + } + } + + wr(reord8((start ? 0x90 : 0x80)+chan),1); // note on, channel # + if (sampled) + wr(reord8(midi_instr),1); + else + wr(reord8(note2note(nr)),1); // note nr + wr(reord8(start ? ampl : 0),1); // velocity +} + +void MidiOut::close() { + if (!init_ok) return; + fclose(midi_f); + init_ok=false; +} +/* +int main() { + midi_out.init("out.mid",3); + midi_out.note_on(0,0,20,4); + midi_out.note_off(0,200,20,0); + midi_out.close(); + if (init_ok) exit(0); exit(1); +} +*/ diff --git a/src/midi-out.h b/src/midi-out.h new file mode 100644 index 0000000..4a20d24 --- /dev/null +++ b/src/midi-out.h @@ -0,0 +1,40 @@ +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; + +struct MidiOut { + FILE *midi_f; + int data_size, + cur_time, + seek_setsize, + cur_track, // current track nr + new_chan, // channel nr, start at 0, skip 9 + sub_div, // from sound.h + meter, + nu_pq, // note units per quarter + us_per_beat; + bool init_ok, + chan_warn; // channel > 16? + int col2midi_instr[groupnr_max][colors_max], // instruments + col2smp_instr[groupnr_max][colors_max], // percussion, channel 10 + col2pan[colors_max], + col_gr2chan[colors_max*groupnr_max], + instr_mapping[groupnr_max][colors_max]; // actual mapping + MidiOut(); + void make_midi(); // in sound.cpp + bool init(const char*,int subd,int nupq,int meter,int us_pb); + void init2(int nr_tracks); + void write_track1(); + void init_track(int tracknr,int group,int col); + void init_perc_track(int tracknr); + void close_track(); + void close(); + void note_onoff(bool start,int col,int grn,int midi_instr,bool sampled,int tim,int nr,int ampl); + int get_chan(bool sampled,int ind); +}; + +extern MidiOut midi_out; +extern bool debug; +extern const float ampl_mult[ampl_max+1]; // from sound.cpp + +void alert(const char *form,...); diff --git a/src/mono-synth.cpp b/src/mono-synth.cpp new file mode 100644 index 0000000..749eb91 --- /dev/null +++ b/src/mono-synth.cpp @@ -0,0 +1,1473 @@ +/* + * Token from: Xsynth - a real-time software synthesizer + * Copyright (C) 1999 S. J. Brookes + * Adapted for Amuc (the Amsterdam Music Composer) by W.Boeke + * + * 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 2 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. + */ + +#include +#include +#include +#include + +#include "amuc-headers.h" + +enum { // same order as control sliders + VCO1_pit, + VCO1_pw, + VCO1_fm, + VCO2_pit, + VCO2_pw, + VCO3_pit, + VCO3_ma, + VCO3_filt, + LFO_fr, + LFO_pw, + LFO_ampl, + LFO_pit, + LFO_filt, // 10 + BAL_o1, + BAL_o2, + BAL_ns, + EG1_att, + EG1_dec, + EG1_sus, + EG1_rel, + EG1_lfo, + PORT_tim, + EG2_att, // 20 + EG2_dec, + EG2_sus, + EG2_rel, + EG2_pit, + EG2_filt, + VCF_coff, + VCF_q, + VOL=NSLIDER-1 +}; + +enum { // same order as checkboxes + VCO_trig, + LFO_synced, + VCF_clip +}; + +enum { // same order as radio buttons + VCO1_wf, + VCO2_wf, + LFO_wf, + Four_pole, + FilterMode +}; + +enum { + LOpass, + BANDpass, + HIpass +}; + +const int + patch_max=100, + rrate=16, // recalculation rate of freq etc. (bigger value may yield clicking attack sound) + DontDis=2; // don't display + +const float + FREQMAX=0.825, // filter only stable to this frequency + PI2=6.28, + eg_topval=0.9, // EG1, EG2 top value + eg_diffval=0.05; + +const Rect + vcf_disp_area(575,280,10,80), + eg1_disp_area(375,225,70,30), + eg2_disp_area(475,225,70,30), + vco1_disp_area(56,80,60,20), + vco2_disp_area(56,350,60,20), + vco3_disp_area(150,80,60,20), + lfo_disp_area(296,80,60,20); +static Array monoSynth; +static RButWinData patch_rbutwin_data; + +static float fminmax(float a, float x, float b) { return x>b ? b : xb ? a : b; } + +struct PatchData { + char *pname, + *patch; +}; + +static Array pdat; +static int lst_patch=-1; + +struct Patches { + Button *buttons[4]; + RButWin *rbutwin; + VScrollbar *scroll; + Patches(Point,WinBase *pwin,int col); + static void scroll_cmd(Id id,int val,int,bool); +}; + +float sinus(float x) { + if (x>0.5) { x-=0.5; return -8*x*(1-2*x); } + if (x<0.5) return 8*x*(1-2*x); + return 0; +} + +struct WaveSrc { + int* triggered; + WaveSrc():triggered(0) { } +}; + +struct SinusWav:WaveSrc { + SinusWav():WaveSrc() { } + float get(float freq,float& pos,bool *trig) { + if (trig) *trig=false; + float res=0; + if (pos>1) { + if (trig) *trig=true; + if (triggered && *triggered) { // triggered = 1, 2 or 3 + if (pos>3) res=0; + else if (pos>2) res= *triggered>=3 ? sinus(pos-2) : 0; + else res= *triggered>=2 ? sinus(pos-1) : 0; + } + else { pos-=1.; res=sinus(pos); } + } + else { + if (pos<0.) pos+=1.; + res=sinus(pos); + } + pos+=freq; + return res; + } +}; + +float triangle(float pos,float pt1,float pt2,float dy1,float dy2) { + if (pospt2) return -1.+(pos-pt2)*dy1; + return 1.-(pos-pt1)*dy2; +} + +struct TriWav:WaveSrc { // triangle wave + TriWav():WaveSrc() { } + float pt1,pt2, + dy1,dy2; + void set(float asym) { // asym: 0.05 -> 0.95 + pt1=asym/2; + pt2=1.-pt1; + dy1=1./pt1; + dy2=2./(pt2-pt1); + } + float get(float freq,float& pos,bool *trig) { + if (trig) *trig=false; + float res=0; + if (pos>1) { + if (trig) *trig=true; + if (triggered && *triggered) { + if (pos>3) res=0; + else if (pos>2) res= *triggered>=3 ? triangle(pos-2,pt1,pt2,dy1,dy2) : 0; + else res= *triggered>=2 ? triangle(pos-1,pt1,pt2,dy1,dy2) : 0; + } + else { pos-=1.; res=triangle(pos,pt1,pt2,dy1,dy2); } + } + else { + if (pos<0.) pos+=1.; + res=triangle(pos,pt1,pt2,dy1,dy2); + } + pos+=freq; + return res; + } +}; + +float square(float pos,float pt1) { + static const float edge=0.01, + dy=1.4/0.01; + if (pos1) { + if (trig) *trig=true; + if (triggered && *triggered) { + if (pos>3) res=-0.7; + else if (pos>2) res= *triggered>=3 ? square(pos-2,pt1) : -0.7; + else res= *triggered>=2 ? square(pos-1,pt1) : -0.7; + } + else { pos-=1.; res=square(pos,pt1); } + } + else { + if (pos<0.) pos+=1.; + res=square(pos,pt1); + } + pos+=freq; + return res; + } +}; + +struct Flutter { + float pos1,pos2,pos3; + static const float mult2=1.7, + mult3=2.7; + Flutter():pos1(0),pos2(0),pos3(0) { } + float get(float freq) { + pos1+=freq; if (pos1>1.) pos1-=1.; + pos2+=freq*mult2; if (pos2>1.) pos2-=1.; + pos3+=freq*mult3; if (pos3>1.) pos3-=1.; + return (sinus(pos1)+sinus(pos2)+sinus(pos3))/3.; + } + void reset() { pos1=pos2=pos3=0.; } +}; +/* +float rnd() { + static int m_white,m_seed; + m_seed = (m_seed * 196314165) + 907633515; + m_white = (m_seed >> 9) | 0x40000000; + return float(m_white)/0x40000000; +}; +*/ +struct Noise { + static const int + pre_dim=100, + dim=100000; // this value is sufficient for percieving true randomness + float pre_buf[pre_dim], + white_buf[dim], + pink_buf[dim]; + Noise() { + float white, + b1=0,b2=0; + for (int i=-pre_dim;i=noise.dim) noise_ind=0; + return noise.pink_buf[noise_ind]; + } +}; + +void setpatch_cmd(Id id,int nr,int fire) { + Synth *synth=monoSynth[id.id1]; + MSynthData ms_data; + if (ms_data.fill_msynth_data(id.id1,pdat[nr].patch)) { + synth->update_values(&ms_data,true); + synth->m_synth->cur_patch=nr; + } +} + +void Synth::button_cmd(Id id) { + int i; + XftColor *textcol; + FILE *fp; + char patch_file[max100], + buf[max100],buf2[max100]; + PatchData *pd; + + switch (id.id2) { + case 'eq': + isa_col=(isa_col+1)%colors_max; + draw_isa_col(true); + set_isa(monoSynth[isa_col]); + break; + case 'ldp': + snprintf(patch_file,max100,"%s/monosynth-patches",cur_dir); + fp=fopen(patch_file,"r"); + if (fp) textcol=xft_Black; + else { + snprintf(patch_file,max100,"%s/monosynth-patches",amuc_data); + fp=fopen(patch_file,"r"); + if (fp) textcol=xft_Blue; + else { + alert("file monosynth-patches not found in %s or in %s",cur_dir,amuc_data); + return; + } + } + lst_patch=-1; + set_patch->rbutwin->empty(); + for (i=0;;++i) { + if (i>=patch_max-1) { alert("patches > %d",patch_max); break; } + char form[20]; + sprintf(form,"%%%ds %%%ds\\n",max100,max100); // form = "%100s %100s\n" + if (fscanf(fp,form,buf,buf2) != 2) + break; + pdat[i].pname=strndup(buf,50); + pdat[i].patch=strndup(buf2,max100); + lst_patch=i; + set_patch->rbutwin->add_rbut(pdat[i].pname,textcol); + } + fclose(fp); + set_patch->scroll->set_range((lst_patch+2)*TDIST); + set_patch->scroll->set_ypos(0); + set_patch->rbutwin->no_actb(); // no active button + break; + case 'modp': + pd=&pdat[set_patch->rbutwin->act_rbutnr()]; + dump_settings(buf,max100,""); + pd->patch=strndup(strchr(buf,':')+1,max100); + break; + case 'addp': + if (lst_patch>=patch_max-2) { alert("patches > %d",patch_max); return; } + ++lst_patch; + pd=&pdat[lst_patch]; + pd->pname=(char*)"NEW"; + set_patch->rbutwin->set_rbut(set_patch->rbutwin->add_rbut("NEW"),0); // add and make active + m_synth->cur_patch=set_patch->rbutwin->act_rbutnr(); + set_patch->scroll->set_range((lst_patch+2)*TDIST); + set_patch->scroll->set_ypos((lst_patch-4)*TDIST); + dump_settings(buf,max100,""); // buf: "set msynth:F1_435 ..." + pd->patch=strndup(strchr(buf,':')+1,max100); + break; + case 'wrp': + if (lst_patch<0) { + alert("0 patches"); + break; + } + snprintf(patch_file,max100,"%s/monosynth-patches",cur_dir); + if ((fp=fopen(patch_file,"w"))==0) { + alert("file %s not opened",patch_file); + break; + } + for (i=0;i<=lst_patch;++i) + fprintf(fp,"%-20s %s\n",pdat[i].pname,pdat[i].patch); + fclose(fp); + alert("file: %s",patch_file); + break; + default: alert("button_cmd: %d",id.id2); + } +} + +static void but_cmd(Id id) { monoSynth[id.id1]->button_cmd(id); } + +void monosynth_uev(int cmd,int id) { // user event: display vcf cutoff + Synth *synth=monoSynth[id]; + MSynth *msynth=synth->m_synth; + BgrWin *bg=synth->vcf_display; + switch (cmd) { + case 'vcfd': { + static const int scale=12; + static float sr=log(SAMPLE_RATE); + int vco1_val=int(scale*(log(msynth->vco1_val)+sr)), + vco2_val=int(scale*(log(msynth->vco2_val)+sr)), + cutoff_val=int(scale*log(msynth->cutoff_val)), + dy=vcf_disp_area.height, + y1=dy-vco1_val+30, + y2=dy-vco2_val+30, + y4=y1-cutoff_val; + bg->clear(); + set_width_color(2,cBlack); + bg->draw_line(Point(0,y1),Point(5,y1)); + bg->draw_line(Point(5,y2),Point(10,y2)); + set_color(cRed); + bg->draw_line(Point(0,y4),Point(10,y4)); + } + break; + case 'clvd': + bg->clear(); + break; + } +} + +Patches::Patches(Point pt,WinBase *pwin,int col) { + button_style.set(1,cForeground,0); + buttons[0]=new Button(pwin->win,Rect(pt.x,pt.y-6,80,14),FN,"load patches",but_cmd,Id(col,'ldp')); + buttons[1]=new Button(pwin->win,Rect(pt.x,pt.y+12,80,14),FN,"update this patch",but_cmd,Id(col,'modp')); + buttons[2]=new Button(pwin->win,Rect(pt.x,pt.y+30,80,14),FN,"add current patch",but_cmd,Id(col,'addp')); + buttons[3]=new Button(pwin->win,Rect(pt.x,pt.y+48,80,14),FN,"save patches to '.'",but_cmd,Id(col,'wrp')); + button_style=def_but_st; + rbutwin=new RButWin(pwin,Rect(pt.x+135,pt.y,130,90),FN,"patches",false,setpatch_cmd,cForeground,Id(col)); + rbutwin->buttons=&patch_rbutwin_data; + scroll=new VScrollbar(pwin->win,Rect(pt.x+258,pt.y,0,90),FN,10*TDIST,scroll_cmd,Id(col)); +} + +void Patches::scroll_cmd(Id id,int val,int,bool) { + Synth *synth=monoSynth[id.id1]; + synth->set_patch->rbutwin->set_y_off(val); +} + +void hide_swin(Id id) { + monoSynth[id.id1]->map_topwin(false); +} + +void draw_panel(Rect exp_rect,Id id) { + Synth *mono_synth=monoSynth[id.id1]; + uint topw=mono_synth->topwin; + XftDraw *xft_topw=mono_synth->xft_topwin; + int i; + static uint cDarkGrey=calc_color("#505050"), + cBgText=calc_color("#707070"); + static const int + xtext[10]={ 30 ,30 ,150,270,150,370,470,590,260,130 }, // labels + ytext[10]={ 15 ,282,15 ,15 ,357,15 ,15 ,15 ,390,282 }, + xbg[10]= { 0 ,0 ,120,240,120,360,460,560,240,120 }, // x label backgrounds + dxbg[10]= { 120,120,120,120,120,100,100,100,120,120 }, // dx label backgrounds + ybg[10]= { 0 ,267,0 ,0 ,342,0 ,0 ,0 ,375,267 }; // y label backgrounds + static const char + *text[10]={ "VCO1","VCO2","VCO3","LFO","MIXER","EG1: ampl","EG2: pitch,filter","VCF","AMPLITUDE","PORTAMENTO" }; + + for(i=0;i<10;++i) { + Rect t_rect(xbg[i],ybg[i],dxbg[i],20); + if (a_in_b(exp_rect,t_rect)) { + fill_rectangle(topw,cBgText,t_rect); + xft_draw_string(xft_topw,xft_White,Point(xtext[i],ytext[i]),text[i],BOLDfont); + } + } + Rect trect(vcf_disp_area.x+15,vcf_disp_area.y+20,80,50); + if (a_in_b(exp_rect,trect)) { + clear(topw,trect); // needed for Xft + xft_draw_string(xft_topw,xft_Red ,Point(trect.x,trect.y+10),"vcf cutoff"); + xft_draw_string(xft_topw,xft_Black,Point(trect.x,trect.y+22),"vco1 freq"); + xft_draw_string(xft_topw,xft_Black,Point(trect.x,trect.y+34),"vco2 freq"); + } + mono_synth->draw_isa_col(false); + static const int + x1[10]={ -1 ,121,121,361,241,120,240,360,460,560 }, // lines + x2[10]={ 120,240,240,660,360,120,240,360,460,560 }, + y1[10]={ 265,340,265,375,375,0 ,0 ,0 ,0 ,0 }, + y2[10]={ 265,340,265,375,375,490,490,490,375,375 }; + + for(i=0;i<10;++i) { + draw_line(topw,1,cWhite,Point(x1[i],y1[i]),Point(x2[i],y2[i])); + draw_line(topw,1,cDarkGrey,Point(x1[i]+1,y1[i]+1),Point(x2[i]+1,y2[i]+1)); + } +} + +void hsl_cmd(Id id,int val,int fire,char *&txt,bool rel) { + const int valmax=16; + val=minmax(0,val,valmax-1); + struct Vals { float val; const char *txt; }; + float cval10=val/10.; + static float volume[valmax]={ 0,0.1,0.15,0.25,0.4,0.6,0.8,1,1.5 }, + filter_q[valmax]={ 1.4,1.0,0.6,0.4,0.3,0.2,0.14,0.1 }, + lfo_freq[valmax]={ 0.2,0.3,0.5,0.8,1.2,2,3,5,8,12,20,30,50,80,120,200 }, + porta_time[valmax]={ 0,0.1,0.2,0.5,1,2,4 }, + semi_exp[valmax]={ 0,0.02,0.04,0.08,0.15,0.3,0.5,0.7,1.4,2. }, + eg_diff[valmax]={ 200,100,60,30,20,10,5,3,2,1.2,0.6 }, + vco_fm[valmax]={ 0,0.1,0.2,0.4,0.8,1.5,2 }; + static Vals + pitch1[valmax]={ + {1.,"0"},{1.002,"0.002"},{1.003,"0.003"},{1.004,"0.004"}, + {1.006,"0.006"},{1.008,"0.008"},{1.01,"0.01"},{1.015,"0.015"},{1.02,"0.02"} + }, + pitch2[valmax]={ + {0.5,"1/2"},{2./3.,"2/3"},{3./4.,"3/4"},{1,"1"},{4./3.,"4/3"},{1.5,"3/2"},{2,"2"},{3,"3"},{4,"4"},{6,"6"},{8,"8"} + }; + int index=id.id2; + Synth *synth=monoSynth[id.id1]; + MSynth *mono_synth=synth->m_synth; + float *c=synth->m_synth->values.cont, + &cont=c[index]; + switch (index) { + case VCO1_pit: // pitch VCO 1 + cont=pitch1[val].val; + set_text(txt,pitch1[val].txt); + break; + case VCO1_fm: // VCO1 FM modulation of VCO2 + cont=vco_fm[val]; + mono_synth->values.is_fm= val>0; + set_text(txt,"%.1f",cont); + break; + case VCO2_pit: // pitch VCO 2 + cont=pitch2[val].val; + set_text(txt,pitch2[val].txt); + if (fire!=DontDis) synth->draw_wave_display(2); + if (mono_synth->values.on_off[VCO_trig]) // update trig_val + mono_synth->values.trig_val= int(cont)>1 ? int(cont)/2 : 1; + break; + case VCO3_pit: // pitch VCO 3 + cont=pitch2[val].val; + set_text(txt,pitch2[val].txt); + if (fire!=DontDis) synth->draw_wave_display(3); + break; + case VCO1_pw: // pulsewidth VCO 1 + cont=fminmax(0.05,val/8.,0.95); + mono_synth->values.tri_wav[VCO1_wf].set(cont); + mono_synth->values.square_wav[VCO1_wf].set(cont); + set_text(txt,"%.2f",cont); + if (fire!=DontDis) synth->draw_wave_display(1,true); + break; + case VCO2_pw: // pulsewidth VCO 2 + cont=fminmax(0.05,val/8.,0.95); + mono_synth->values.tri_wav[VCO2_wf].set(cont); + mono_synth->values.square_wav[VCO2_wf].set(cont); + set_text(txt,"%.2f",cont); + if (fire!=DontDis) synth->draw_wave_display(2,true); + break; + case BAL_o1: // mix VCO1 + case BAL_o2: // mix VCO2 + case BAL_ns: // mix noise + cont=val/5.; + set_text(txt,"%.1f",cont); + break; + case EG1_sus: // sustain level + cont=cval10*eg_topval; + set_text(txt,"%.0f%%",cval10*100); + if (fire!=DontDis) synth->draw_eg_display(1); + break; + case EG2_sus: // sustain level + cont=cval10*eg_topval; + set_text(txt,"%.0f%%",cval10*100); + if (fire!=DontDis) synth->draw_eg_display(2); + break; + case VCO3_ma: // VCO3 mod amplitude + if (val>=9) { + mono_synth->values.ringm_vco=true; + cont=0; set_text(txt,"ring mod."); + } + else { + mono_synth->values.ringm_vco=false; + cont=val/8.; set_text(txt,"%.0f%%",cont*100); + } + break; + case VCO3_filt: // VCO3 mod filter + case EG2_pit: // pitch depth + cont=semi_exp[val]; + set_text(txt,"%.0f%%",cont*100); + break; + case LFO_ampl: // amplitude depth LFO + if (val>=9) { + mono_synth->values.ringm_lfo=true; + cont=0; set_text(txt,"ring mod."); + } + else { + mono_synth->values.ringm_lfo=false; + cont=val/8.; set_text(txt,"%.0f%%",cont*100); + } + break; + case LFO_filt: // filter depth LFO + cont=semi_exp[val]; + set_text(txt,"+/- %.2f",cont); + break; + case VOL: // volume + cont=volume[val]; + set_text(txt,"%.2f",cont); + break; + case EG1_lfo: // lfo control by EG1 + cont=min(val,2)/2.; + break; + case LFO_fr: // lfo frequency + cont=lfo_freq[val]; + set_text(txt,"%.1f",cont); + break; + case LFO_pw: // pulsewidth LFO + cont=fminmax(0.05,val/8.,0.95); + mono_synth->values.tri_wav[LFO_wf].set(cont); + mono_synth->values.square_wav[LFO_wf].set(cont); + set_text(txt,"%.2f",cont); + if (fire!=DontDis) synth->draw_wave_display(4,true); + break; + case LFO_pit: // pitch depth LFO + cont=semi_exp[val]*0.35; + set_text(txt,"+/- %.3f",cont); + break; + case PORT_tim: // glide time + if (val) { + cont=porta_time[val]; + set_text(txt,"%.2f",cont); + cont=1-rrate/cont/SAMPLE_RATE; + mono_synth->values.is_porta=true; + } + else { + cont=0; + set_text(txt,"0"); + mono_synth->values.is_porta=false; + } + break; + case EG1_att: // attack time EG 1 + case EG1_dec: // decay time EG 1 + case EG1_rel: // release time EG 1 + cont=eg_diff[val]; + if (index==EG1_att) mono_synth->values.eg1_attack_0= val==0; + set_text(txt,"%.2f",1/cont); + cont=cont*rrate/SAMPLE_RATE; + if (fire!=DontDis) { synth->draw_eg_display(1); if (rel) synth->draw_eg_display(2); } // draw both + break; + case EG2_att: // attack time EG 2 + case EG2_dec: // decay time EG 2 + case EG2_rel: // release time EG 2 + cont=eg_diff[val]; + if (index==EG2_att) mono_synth->values.eg2_attack_0= val==0; + set_text(txt,"%.2f",1/cont); + cont=cont*rrate/SAMPLE_RATE; + if (fire!=DontDis) synth->draw_eg_display(2); + break; + case EG2_filt: // filter modulation EG 2 + cont=20*semi_exp[val]; + set_text(txt,"%.1f",cont); + break; + case VCF_coff: // cutoff VCF + cont=val-4.; // = 15.*(val/15.)-4.; + set_text(txt,"%.1f",float(cont-1.)); + break; + case VCF_q: // resonance VCF + cont=filter_q[val]; + set_text(txt,"%.1f",1/cont); + break; + default: + alert("hsl_cmd: case %d?",index); + } +} + +static void rbut_cmd(Id id,int nr,int fire) { + int index=id.id2; + Synth *synth=monoSynth[id.id1]; + MSynth *mono_synth=synth->m_synth; + switch (index) { + case VCO1_wf: + mono_synth->values.dete[index]=nr; + if (fire!=DontDis) synth->draw_wave_display(1); + break; + case VCO2_wf: + mono_synth->values.dete[index]=nr; + if (fire!=DontDis) synth->draw_wave_display(2); + break; + case LFO_wf: + mono_synth->values.dete[index]=nr; + if (fire!=DontDis) synth->draw_wave_display(4); + break; + case Four_pole: + mono_synth->values.dete[index]=nr; + mono_synth->values.is_pole4= nr==0; + break; + case FilterMode: + mono_synth->values.dete[index]=nr; + mono_synth->values.filter_mode= nr==0 ? BANDpass : nr==1 ? LOpass : HIpass; + break; + default: + alert("case %d?\n",index); + } +} + +void chbox_cmd(Id id,bool on) { + int index=id.id2; + MSynth *mono_synth=monoSynth[id.id1]->m_synth; + switch (index) { + case VCO_trig: + if (on) { + int pmult=int(mono_synth->values.cont[VCO2_pit]); + mono_synth->values.trig_val=pmult>1 ? pmult/2 : 1; + } + else mono_synth->values.trig_val=0; + // no break; + case LFO_synced: + case VCF_clip: + mono_synth->values.on_off[index]=on; + break; + default: + alert("case %d?\n",index); + } +} + +RButWin *waveform_win(int x,int y,Synth *synth,int id) { + int dy1= id==LFO_wf ? 5*TDIST : 3*TDIST; + RButWin *rbw=new RButWin(synth->subw,Rect(x,y,50,dy1),FN,"wave",false,rbut_cmd,cForeground,Id(synth->col,id)); + rbw->add_rbut("sine"); + rbw->add_rbut("triangle"); + rbw->add_rbut("pulse"); + if (id==LFO_wf) { + rbw->add_rbut("noise"); rbw->add_rbut("flutter"); + } + return rbw; +} + +void MSynth::reset(bool soft) { // rest is set by set_values() + note_on=0; + prev_note=0; + if (!soft) { // mode != repeat + osc1_pos=osc2_pos=osc3_pos=lfo_pos=0.; + d1=d2=d3=d4=0.; + eg1=eg2=0.; + eg1_phase=eg2_phase=0; + cur_patch=nop; + } +} + +void Synth::init(bool soft) { m_synth->reset(soft); } +void Synth::set_values(int mnr,float f) { m_synth->set_values(mnr,f); } +bool Synth::fill_buffer(float *buf,const int buf_size,const float amp_mul) { + bool res=m_synth->fill_buffer(buf,buf_size,amp_mul); + if (res) send_uev('vcfd',col); // VCF display + else send_uev('clvd',col); // clear VCF display + return res; +} +void Synth::note_off() { + if (m_synth->note_on>0) --m_synth->note_on; +} + +static void strip_points(Point *points,int& nr) { // decrease points + if (nr<=2) return; + Point pt0(points[0]), + pt1(points[1]); + bool horizontal= abs(pt1.y-pt0.y) < abs(pt1.x-pt1.x); + float angle=horizontal ? float(pt1.y-pt0.y) / (pt1.x-pt0.x) : float(pt1.x-pt0.x) / (pt1.y-pt0.y); + int i, + ind=0; + for (i=2;;++i) { + bool horizontal2= abs(points[i].y-points[i-1].y) < abs(points[i].x-points[i-1].x); + float angle2=horizontal2 ? float(points[i].y-points[i-1].y) / (points[i].x-points[i-1].x) : + float(points[i].x-points[i-1].x) / (points[i].y-points[i-1].y); + if (horizontal!=horizontal2 || fabs(angle-angle2)>0.1) { + points[++ind]=points[i-1]; + angle=angle2; + horizontal=horizontal2; + // printf("ind=%d x=%d y=%d hor=%d\n",ind,points[ind].x,points[ind].y,horizontal); + } + if (i==nr-1) { + points[++ind]=points[i]; + break; + } + } + nr=ind+1; +} + +void Synth::draw_wave_display(int vco_nr,bool test_whether_needed) { // default: test_whether_needed = false + BgrWin *bgwin= vco_nr==1 ? vco1_display : vco_nr==2 ? vco2_display : vco_nr==3 ? vco3_display : lfo_display; + if (bgwin->is_hidden()) return; + const int dx=vco1_disp_area.width, + dy=vco1_disp_area.height; + int waveform=0, + wf_index; + float wf_pw=0, + wf_pit=1; + switch (vco_nr) { + case 1: + waveform=m_synth->values.dete[VCO1_wf]; + wf_index=VCO1_wf; wf_pw=m_synth->values.cont[VCO1_pw]; + break; + case 2: + waveform=m_synth->values.dete[VCO2_wf]; + wf_index=VCO2_wf; wf_pw=m_synth->values.cont[VCO2_pw]; + wf_pit=m_synth->values.cont[VCO2_pit]; + break; + case 3: + wf_index=0; + wf_pit=m_synth->values.cont[VCO3_pit]; + break; + case 4: + waveform=m_synth->values.dete[LFO_wf]; + wf_index=LFO_wf; wf_pw=m_synth->values.cont[LFO_pw]; + break; + } + if (test_whether_needed && (waveform==0 || waveform==3)) + return; + Point points[dx]; + int i,x1,x2, + val, + nr_points=0; + float inc, + fi; + switch (waveform) { + case 0: // sinus + inc=wf_pit/dx; + for (i=0,fi=0;i<=dx;++i) { + points[i].set(i,int(dy-dy*sinus(fi))/2); + if ((fi+=inc)>1.) fi-=1.; + } + nr_points=dx+1; + strip_points(points,nr_points); + break; + case 1: // triangle + for (i=0;;i+=2) { + x1=int(i*dx/2/wf_pit); x2=x1+int(wf_pw*dx/wf_pit); + points[i].set(x1,dy); + points[i+1].set(x2,0); + if (x2>=dx-1) { nr_points=i+2; break; } + } + break; + case 2: // pulse + val=dy*7/10; + for (i=0;;i+=4) { + x1=int(i*dx/4/wf_pit); x2=x1+int(wf_pw*dx/wf_pit); + points[i+1].set(x1,(dy-val)/2); points[i+2].set(x2,(dy-val)/2); + points[i].set(x1,(dy+val)/2); points[i+3].set(x2,(dy+val)/2); + if (x2>=dx-1) { nr_points=i+4; break; } + } + break; + case 3: // white noise + for (i=0;i<=dx;i+=2) { + points[i/2].set(i,int(dy*(2.+noise.white_buf[i])/4)); + } + nr_points=dx/2+1; + break; + case 4: // flutter + inc=wf_pit/dx; + for (i=0,fi=0;i<=dx;++i) { + float fi2=fi*Flutter::mult2; while (fi2>1.) fi2-=1.; + float fi3=fi*Flutter::mult3; while (fi3>1.) fi3-=1.; + float fval=dy*(sinus(fi)+sinus(fi2)+sinus(fi3))/3.; + points[i].set(i,int(dy-fval)/2); + if ((fi+=inc)>1.) fi-=1.; + } + nr_points=dx+1; + strip_points(points,nr_points); + break; + } + bgwin->clear(); + bgwin->cai_draw_lines(points,nr_points,1,cai_Blue); +} + +void Synth::draw_eg_display(int eg_nr) { + BgrWin *bgwin= eg_nr==1 ? eg1_display : eg2_display; + if (bgwin->is_hidden()) return; + const int dx=eg1_disp_area.width, + dy=eg1_disp_area.height; + float c=dx*2; + bgwin->clear(); + int i; + float *cont=m_synth->values.cont, + a=cont[EG1_att], + d=cont[EG1_dec], + s=cont[EG1_sus], + r=cont[EG1_rel]; + bool eg_attack_0=m_synth->values.eg1_attack_0; + mult_eg=0.07*fmax(1/a + (eg_topval-s)/d + s/r, c); // EG1 used for scaling + if (eg_nr==2) { + a=cont[EG2_att]; d=cont[EG2_dec]; s=cont[EG2_sus]; r=cont[EG2_rel]; eg_attack_0=m_synth->values.eg2_attack_0; + } + //printf("w=%p a=%.2f d=%.2f s=%.2f r=%.2f\n",bgwin,a,d,s,r); + a*=mult_eg; + d*=mult_eg; + s*=dy; + r*=mult_eg; + c/=mult_eg; + if (c<8) c=8; // better display in case s has high value + Point points[dx]; + float val=0; + + if (eg_attack_0) { + val=eg_topval*dy; + points[0].set(0,dy-int(val)); + i=1; + } + else + for (i=0;i eg_topval*dy) { + val=eg_topval*dy; + points[i].set(i,dy-int(val)); + break; + } + points[i].set(i,dy-int(val)); + val+=a*(dy-val); + } + for (;i0.1) val-=r*val; + else if (val>0.) val-=r*0.1; + else { + val=0; nr_pts=i; + points[i].set(i,dy); + break; + } + points[i].set(i,dy-int(val)); + } + set_color(cGrey); + bgwin->fill_rectangle(Rect(start_c,0,int(c),dy)); + strip_points(points,nr_pts); + bgwin->cai_draw_lines(points,nr_pts,1,cai_Blue); +} + +static void draw_eg_dis(Id id) { monoSynth[id.id1]->draw_eg_display(id.id2); } + +static void draw_wave_dis(Id id) { monoSynth[id.id1]->draw_wave_display(id.id2); } + +Synth::Synth(Point top,int c,bool do_map): + m_synth(new MSynth()), + col(c), + isa_col(c) { + monoSynth[col]=this; + static uint cDispBgr=cBackground; + subw=new SubWin("Mono Synth",Rect(top.x,top.y,660,490),do_map,cForeground,draw_panel,hide_swin,Id(col)); + topwin=subw->win; xft_topwin=subw->xft_win; + // VCO 1 + hsliders[VCO1_pit]=new HSlider(subw,Rect(15,40,68,0),FN,0,8,"detune","0","8",hsl_cmd,cForeground,Id(col,VCO1_pit)); + hsliders[VCO1_pw]=new HSlider(subw,Rect(15,145,68,0),FN,0,8,"waveform","0","8",hsl_cmd,cForeground,Id(col,VCO1_pw)); + hsliders[VCO1_fm]=new HSlider(subw,Rect(10,190,60,0),FN,0,6,"FM VCO2 ","0","6",hsl_cmd,cForeground,Id(col,VCO1_fm)); + checkboxs[VCO_trig]=new CheckBox(topwin,Rect(10,235,0,0),FN,cForeground,"trigger VCO2 ",chbox_cmd,Id(col,VCO_trig)); + + // VCO 2 + hsliders[VCO2_pit]=new HSlider(subw,Rect(15,310,90,0),FN,0,10,"pitch mult.","0","10",hsl_cmd,cForeground,Id(col,VCO2_pit)); + hsliders[VCO2_pw]=new HSlider(subw,Rect(15,415,68,0),FN,0,8,"waveform","0","8",hsl_cmd,cForeground,Id(col,VCO2_pw)); + + // VCO 3 + hsliders[VCO3_pit]=new HSlider(subw,Rect(135,40,90,0),FN,0,10,"pitch mult.","0","10",hsl_cmd,cForeground,Id(col,VCO3_pit)); + hsliders[VCO3_ma]=new HSlider(subw,Rect(135,145,68,0),FN,0,9,"ampl. mod.","0","9",hsl_cmd,cForeground,Id(col,VCO3_ma)); + hsliders[VCO3_filt]=new HSlider(subw,Rect(135,190,68,0),FN,0,8,"filter mod.","0","8",hsl_cmd,cForeground,Id(col,VCO3_filt)); + + // PORTAMENTO + hsliders[PORT_tim]=new HSlider(subw,Rect(135,310,68,0),FN,0,6,"glide time","0","6",hsl_cmd,cForeground,Id(col,PORT_tim)); + + // LFO + hsliders[LFO_fr]=new HSlider(subw,Rect(245,40,110,0),FN,0,15,"frequency","0","15",hsl_cmd,cForeground,Id(col,LFO_fr)); + hsliders[LFO_pw]=new HSlider(subw,Rect(255,170,72,0),FN,0,8,"waveform","0","8",hsl_cmd,cForeground,Id(col,LFO_pw)); + hsliders[LFO_ampl]=new HSlider(subw,Rect(255,214,82,0),FN,0,9,"ampl. mod.","0","9",hsl_cmd,cForeground,Id(col,LFO_ampl)); + hsliders[LFO_pit]=new HSlider(subw,Rect(255,258,82,0),FN,0,9,"VCO2 mod.","0","9",hsl_cmd,cForeground,Id(col,LFO_pit)); + hsliders[LFO_filt]=new HSlider(subw,Rect(255,302,72,0),FN,0,8,"filter mod.","0","8",hsl_cmd,cForeground,Id(col,LFO_filt)); + + // MIXER + hsliders[BAL_o1]=new HSlider(subw,Rect(135,380,55,0),FN,0,5,"vco1","0","5",hsl_cmd,cForeground,Id(col,BAL_o1)); + hsliders[BAL_o2]=new HSlider(subw,Rect(135,420,55,0),FN,0,5,"vco2","0","5",hsl_cmd,cForeground,Id(col,BAL_o2)); + hsliders[BAL_ns]=new HSlider(subw,Rect(135,460,55,0),FN,0,5,"noise","0","5",hsl_cmd,cForeground,Id(col,BAL_ns)); + + // EG 1 + hsliders[EG1_att]=new HSlider(subw,Rect(375,40,74,0),FN,0,10,"attack time","0","10",hsl_cmd,cForeground,Id(col,EG1_att)); + hsliders[EG1_dec]=new HSlider(subw,Rect(375,90,74,0),FN,0,10,"decay time","0","10",hsl_cmd,cForeground,Id(col,EG1_dec)); + hsliders[EG1_sus]=new HSlider(subw,Rect(375,140,74,0),FN,0,10,"sustain level","0","10",hsl_cmd,cForeground,Id(col,EG1_sus)); + hsliders[EG1_rel]=new HSlider(subw,Rect(375,190,74,0),FN,0,10,"release time","0","10",hsl_cmd,cForeground,Id(col,EG1_rel)); + hsliders[EG1_lfo]=new HSlider(subw,Rect(375,295,30,0),FN,0,2,"LFO effect","0","2",hsl_cmd,cForeground,Id(col,EG1_lfo)); + + // EG 2 + hsliders[EG2_att]=new HSlider(subw,Rect(475,40,74,0),FN,0,10,"attack time","0","10",hsl_cmd,cForeground,Id(col,EG2_att)); + hsliders[EG2_dec]=new HSlider(subw,Rect(475,90,74,0),FN,0,10,"decay time","0","10",hsl_cmd,cForeground,Id(col,EG2_dec)); + hsliders[EG2_sus]=new HSlider(subw,Rect(475,140,74,0),FN,0,10,"sustain level","0","10",hsl_cmd,cForeground,Id(col,EG2_sus)); + hsliders[EG2_rel]=new HSlider(subw,Rect(475,190,74,0),FN,0,10,"release time","0","10",hsl_cmd,cForeground,Id(col,EG2_rel)); + hsliders[EG2_pit]=new HSlider(subw,Rect(475,295,74,0),FN,0,8,"VCO2 mod.","0","8",hsl_cmd,cForeground,Id(col,EG2_pit)); + hsliders[EG2_filt]=new HSlider(subw,Rect(475,345,74,0),FN,0,8,"filter mod.","0","8",hsl_cmd,cForeground,Id(col,EG2_filt)); + + // VCF + hsliders[VCF_coff]=new HSlider(subw,Rect(565,40,90,0),FN,0,15,"cutoff","0","15",hsl_cmd,cForeground,Id(col,VCF_coff)); + hsliders[VCF_q]=new HSlider(subw,Rect(565,90,68,0),FN,0,7,"resonance","0","7",hsl_cmd,cForeground,Id(col,VCF_q)); + + // VOLUME + hsliders[VOL]=new HSlider(subw,Rect(250,420,90,0),FN,0,8,"master ampl.","0","8",hsl_cmd,cForeground,Id(col,VOL)); + ms_ampl=&hsliders[VOL]->value(); + + rbutwins[VCO1_wf]=waveform_win(2,80,this,VCO1_wf); + vco1_display=new BgrWin(topwin,vco1_disp_area,FN,draw_wave_dis,cDispBgr,1,Id(col,1)); + + rbutwins[VCO2_wf]=waveform_win(2,350,this,VCO2_wf); + vco2_display=new BgrWin(topwin,vco2_disp_area,FN,draw_wave_dis,cDispBgr,1,Id(col,2)); + + vco3_display=new BgrWin(topwin,vco3_disp_area,FN,draw_wave_dis,cDispBgr,1,Id(col,3)); + + rbutwins[LFO_wf]=waveform_win(242,80,this,LFO_wf); + lfo_display=new BgrWin(topwin,lfo_disp_area,FN,draw_wave_dis,cDispBgr,1,Id(col,4)); + + RButWin *rbw; + rbutwins[Four_pole]=rbw=new RButWin(subw,Rect(575,135,60,2*TDIST),FN,"slope",false,rbut_cmd,cForeground,Id(col,Four_pole)); + rbw->add_rbut("24 db/oct"); + rbw->add_rbut("12 db/oct"); + + rbutwins[FilterMode]=rbw=new RButWin(subw,Rect(575,190,60,3*TDIST),FN,"mode",false,rbut_cmd,cForeground,Id(col,FilterMode)); + rbw->add_rbut("bandpass"); + rbw->add_rbut("lowpass"); + rbw->add_rbut("highpass"); + + checkboxs[VCF_clip]=new CheckBox(topwin,Rect(574,245,0,0),FN,cForeground,"clip",chbox_cmd,Id(col,VCF_clip)); + + vcf_display=new BgrWin(topwin,vcf_disp_area,FN,0,cWhite); + eg1_display=new BgrWin(topwin,eg1_disp_area,FN,draw_eg_dis,cDispBgr,1,Id(col,1)); + eg2_display=new BgrWin(topwin,eg2_disp_area,FN,draw_eg_dis,cDispBgr,1,Id(col,2)); + + checkboxs[LFO_synced]=new CheckBox(topwin,Rect(255,335,0,0),FN,cForeground,"sync'ed",chbox_cmd,Id(col,LFO_synced)); + + set_patch=new Patches(Point(370,395),subw,col); + + button_style.param=1; + eq=new Button(topwin,Rect(10,470,28,0),FN,"eq?",but_cmd,Id(col,'eq')); + button_style.param=0; + + MSynthData ms_data; + int i; + ms_data.fill_msynth_data(col,"F5,32300,314,346,5000021,500,17680,0,065703,64010,5"); + for (i=0;iset_cbval(ms_data.cb_buf[i],DontDis,false); // fire, not draw + for (i=0;iset_hsval(ms_data.hsl_buf[i],DontDis,false); // should come after checkboxes, because of VCO2_pit + for (i=0;iset_rbutnr(ms_data.rb_buf[i],DontDis,false); +} + +void Synth::map_topwin(bool do_map) { + if (do_map) { + set_patch->scroll->set_range((lst_patch+2)*TDIST); + map_window(topwin); + } + else + hide_window(topwin); +} + +MSynth::MSynth(): + isa(&values), + noise_ind(0) { + reset(false); +} + +float MSynth::oscillator(int vco_nr,float& pos,float osc_freq,int waveform,bool *trig) { + //float osc_freq=omega/SAMPLE_RATE; + const int nmult=20; // pos multiplier in case of lfo noise + static const float ndim=float(noise.dim/nmult); + switch(waveform) { + case 0: // sine wave + return isa->sin_wav[vco_nr].get(osc_freq,pos,trig); + case 1: // triangle wave + return isa->tri_wav[vco_nr].get(osc_freq,pos,trig); + case 2: // pulse wave + return isa->square_wav[vco_nr].get(osc_freq,pos,trig); + case 3: // noise + pos+=osc_freq; + if (pos>ndim) pos-=ndim; + else if (pos<0) pos+=ndim; + return noise.white_buf[int(pos*nmult)]; + case 4: // flutter wave + return isa->flutter_wav.get(osc_freq); + default: + alert("waveform %d?",waveform); + return 0.; + } +} + +void Synth::dump_settings(char *buf,int bmax,const char *c) { + HSlider **hsl=hsliders; + RButWin **rb=rbutwins; + CheckBox **cb=checkboxs; + snprintf(buf,bmax,"set %s msynth:F5,%x%x%x%x%x,%x%x%x,%x%x%x,%x%x%x%x%x%x%d,%x%x%x,%x%x%x%x%x,%x,%x%x%x%x%x%x,%x%x%x%x%d,%x", + c, + hsl[VCO1_pit]->value(),rb[VCO1_wf]->act_rbutnr(),hsl[VCO1_pw]->value(),hsl[VCO1_fm]->value(),cb[VCO_trig]->value, // VCO1 + hsl[VCO2_pit]->value(),rb[VCO2_wf]->act_rbutnr(),hsl[VCO2_pw]->value(), // VCO2 + hsl[VCO3_pit]->value(),hsl[VCO3_ma]->value(),hsl[VCO3_filt]->value(), // VCO3 + hsl[LFO_fr]->value(),rb[LFO_wf]->act_rbutnr(),hsl[LFO_pw]->value(),hsl[LFO_ampl]->value(), // LFO + hsl[LFO_pit]->value(),hsl[LFO_filt]->value(),cb[LFO_synced]->value, // LFO + hsl[BAL_o1]->value(),hsl[BAL_o2]->value(),hsl[BAL_ns]->value(), // MIXER + hsl[EG1_att]->value(),hsl[EG1_dec]->value(),hsl[EG1_sus]->value(),hsl[EG1_rel]->value(),hsl[EG1_lfo]->value(), // EG1 + hsl[PORT_tim]->value(), // PORTAMENTO + hsl[EG2_att]->value(),hsl[EG2_dec]->value(),hsl[EG2_sus]->value(),hsl[EG2_rel]->value(),hsl[EG2_pit]->value(),hsl[EG2_filt]->value(), // EG2 + hsl[VCF_coff]->value(),hsl[VCF_q]->value(),rb[Four_pole]->act_rbutnr(),rb[FilterMode]->act_rbutnr(),cb[VCF_clip]->value, // VCF + hsl[VOL]->value()); // VOLUME +} + +bool MSynthData::fill_msynth_data(int _col,const char *str) { + col=_col; + int n=0; + n=sscanf(str,"F5,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx", + hsl_buf+VCO1_pit,rb_buf+VCO1_wf,hsl_buf+VCO1_pw,hsl_buf+VCO1_fm,cb_buf+VCO_trig, // VCO 1 + hsl_buf+VCO2_pit,rb_buf+VCO2_wf,hsl_buf+VCO2_pw, // VCO 2 + hsl_buf+VCO3_pit,hsl_buf+VCO3_ma,hsl_buf+VCO3_filt, // VCO 3 + hsl_buf+LFO_fr,rb_buf+LFO_wf,hsl_buf+LFO_pw,hsl_buf+LFO_ampl,hsl_buf+LFO_pit,hsl_buf+LFO_filt,cb_buf+LFO_synced, // LFO + hsl_buf+BAL_o1,hsl_buf+BAL_o2,hsl_buf+BAL_ns, // MIXER + hsl_buf+EG1_att,hsl_buf+EG1_dec,hsl_buf+EG1_sus,hsl_buf+EG1_rel,hsl_buf+EG1_lfo, // EG 1 + hsl_buf+PORT_tim, // PORTAMENTO + hsl_buf+EG2_att,hsl_buf+EG2_dec,hsl_buf+EG2_sus,hsl_buf+EG2_rel,hsl_buf+EG2_pit,hsl_buf+EG2_filt, // EG 2 + hsl_buf+VCF_coff,hsl_buf+VCF_q,rb_buf+Four_pole,rb_buf+FilterMode,cb_buf+VCF_clip, // VCF + hsl_buf+VOL); // VOLUME + if (n==0) { // old format + n=sscanf(str,"F4_%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%*c%*c%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx", + hsl_buf+VCO1_pit,rb_buf+VCO1_wf,hsl_buf+VCO1_pw,hsl_buf+VCO1_fm, // VCO 1 + hsl_buf+VCO2_pit,rb_buf+VCO2_wf,hsl_buf+VCO2_pw, // VCO 2 + hsl_buf+VCO3_pit,hsl_buf+VCO3_ma,hsl_buf+VCO3_filt, // VCO 3 + hsl_buf+LFO_fr,rb_buf+LFO_wf,hsl_buf+LFO_pw,hsl_buf+LFO_ampl,hsl_buf+LFO_pit,hsl_buf+LFO_filt,cb_buf+LFO_synced, // LFO + hsl_buf+BAL_o1,hsl_buf+BAL_o2,hsl_buf+BAL_ns, // MIXER + hsl_buf+EG1_att,hsl_buf+EG1_dec,hsl_buf+EG1_sus,hsl_buf+EG1_rel,hsl_buf+EG1_lfo, // EG 1 + hsl_buf+PORT_tim, // PORTAMENTO + hsl_buf+EG2_att,hsl_buf+EG2_dec,hsl_buf+EG2_sus,hsl_buf+EG2_rel,hsl_buf+EG2_pit,hsl_buf+EG2_filt, // EG 2 + hsl_buf+VCF_coff,hsl_buf+VCF_q,rb_buf+Four_pole,rb_buf+FilterMode,cb_buf+VCF_clip, // VCF + hsl_buf+VOL); // VOLUME + cb_buf[VCO_trig]=false; + if (n>0) n+=1; + } + if (n==0) { + n=sscanf(str,"F3_%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx", + hsl_buf+VCO1_pit,rb_buf+VCO1_wf,hsl_buf+VCO1_pw,hsl_buf+VCO1_fm, // VCO 1 + hsl_buf+VCO2_pit,rb_buf+VCO2_wf,hsl_buf+VCO2_pw,hsl_buf+VCO3_ma,hsl_buf+VCO3_filt, // VCO 2 + hsl_buf+LFO_fr,rb_buf+LFO_wf,hsl_buf+LFO_pw,hsl_buf+LFO_ampl,hsl_buf+LFO_pit,hsl_buf+LFO_filt,cb_buf+LFO_synced, // LFO + hsl_buf+BAL_o1,hsl_buf+BAL_o2,hsl_buf+BAL_ns, // MIXER + hsl_buf+EG1_att,hsl_buf+EG1_dec,hsl_buf+EG1_sus,hsl_buf+EG1_rel,hsl_buf+EG1_lfo, // EG 1 + hsl_buf+PORT_tim, // PORTAMENTO + hsl_buf+EG2_att,hsl_buf+EG2_dec,hsl_buf+EG2_sus,hsl_buf+EG2_rel,hsl_buf+EG2_pit,hsl_buf+EG2_filt, // EG 2 + hsl_buf+VCF_coff,hsl_buf+VCF_q,rb_buf+Four_pole,rb_buf+FilterMode,cb_buf+VCF_clip, // VCF + hsl_buf+VOL); // VOLUME + hsl_buf[VCO3_pit]=hsl_buf[VCO2_pit]; + cb_buf[VCO_trig]=false; + if (n>0) n+=2; + } + if (n==0) { + n=sscanf(str,"F2_%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx,%1hhx%1hhx%1hhx%1hhx%1hhx%1hhx,%1hhx%1hhx%1hhx%1hhx,%1hhx", + hsl_buf+VCO1_pit,rb_buf+VCO1_wf,hsl_buf+VCO1_pw, // VCO 1 + hsl_buf+VCO2_pit,rb_buf+VCO2_wf,hsl_buf+VCO2_pw,hsl_buf+VCO3_ma,hsl_buf+VCO3_filt, // VCO 2 + hsl_buf+LFO_fr,rb_buf+LFO_wf,hsl_buf+LFO_pw,hsl_buf+LFO_ampl,hsl_buf+LFO_pit,hsl_buf+LFO_filt,cb_buf+LFO_synced, // LFO + hsl_buf+BAL_o1,hsl_buf+BAL_o2,hsl_buf+BAL_ns, // MIXER + hsl_buf+EG1_att,hsl_buf+EG1_dec,hsl_buf+EG1_sus,hsl_buf+EG1_rel,hsl_buf+EG1_lfo, // EG 1 + hsl_buf+PORT_tim, // PORTAMENTO + hsl_buf+EG2_att,hsl_buf+EG2_dec,hsl_buf+EG2_sus,hsl_buf+EG2_rel,hsl_buf+EG2_pit,hsl_buf+EG2_filt, // EG 2 + hsl_buf+VCF_coff,hsl_buf+VCF_q,rb_buf+Four_pole,rb_buf+FilterMode, // VCF + hsl_buf+VOL); // VOLUME + cb_buf[VCF_clip]=0; + hsl_buf[VCO1_fm]=0; + hsl_buf[VCO3_pit]=hsl_buf[VCO2_pit]; + cb_buf[VCO_trig]=false; + if (n>0) n+=4; + } + if (n!=NCONTROLS) { + if (n!=NCONTROLS) alert("set msynth: %d values read (expected %d)",n,NCONTROLS); + else alert("set msynth: %d values read (expected %d)",n,NCONTROLS); + alert("string: %s",str); + return false; + } + return true; +} + +void Synth::set_isa(Synth *other) { + if (other==this) { + isa_col=col; + ms_ampl=&hsliders[VOL]->value(); + m_synth->isa=&m_synth->values; + } + else { + isa_col=other->col; + m_synth->isa=&other->m_synth->values; + ms_ampl=other->ms_ampl; + } +} + +void Synth::draw_isa_col(bool clear) { + if (isa_col==col && !clear) return; + uint line_col= isa_col==col ? cForeground : col2color(isa_col); + draw_line(topwin,2,line_col,Point(1,0),Point(1,subw->dy)); +} + +void Synth::update_values(MSynthData *d,bool setpatch) { + int i, + val; + bool dr_eg=false, + dr_wav1=false,dr_wav2=false,dr_wav3=false,dr_wav4=false; + for (i=0;ihsl_buf[i]; + if (hsliders[i]->value()!=val) { + switch (i) { + case EG1_att: case EG1_dec: case EG1_sus: case EG1_rel: + case EG2_att: case EG2_dec: case EG2_sus: case EG2_rel: + dr_eg=true; + break; + case VCO1_pw: dr_wav1=true; break; + case VCO2_pw: case VCO2_pit: dr_wav2=true; break; + case LFO_pw: dr_wav4=true; break; + } + if (setpatch) // sent by setpatch_cmd() + hsliders[i]->set_hsval(val,DontDis,true); // set value, fire, draw + else { + hsliders[i]->set_hsval(val,DontDis,false); // fire, not draw + send_uev('dr66',col,i); // draw sliders + } + } + } + if (dr_eg) send_uev('dr69',col,i); // draw eg display + + for (i=0;irb_buf[i]; + if (rbutwins[i]->act_rbutnr() != val) { + switch (i) { + case VCO1_wf: dr_wav1=true; break; + case VCO2_wf: dr_wav2=true; break; + case LFO_wf: dr_wav4=true; break; + } + if (setpatch) + rbutwins[i]->set_rbutnr(val,DontDis,true); // set value, fire and draw + else { + rbutwins[i]->set_rbutnr(val,DontDis,false); + send_uev('dr67',col,i); // draw radio buttons + } + } + } + if (dr_wav1) send_uev('dr70',col,i); // draw waveforms + if (dr_wav2) send_uev('dr71',col,i); + if (dr_wav3) send_uev('dr72',col,i); + if (dr_wav4) send_uev('dr73',col,i); + for (i=0;icb_buf[i]; + if (checkboxs[i]->value!=val) { + if (setpatch) + checkboxs[i]->set_cbval(val,1,true); // set value, fire and draw + else { + checkboxs[i]->set_cbval(val,1,false); + send_uev('dr68',col,i); // draw check boxes + } + } + } +} + +void MSynth::set_values(int mnr,float f) { + if (note_on) { + note_diff= prev_notemnr ? -1 : 0; + if (isa->is_porta) eg1_phase=eg2_phase=1; + } + else { + note_diff=0; + fund_pitch=f; + } + if (!note_on || !isa->is_porta) { // NOT reset d1 etc, eg1, eg2, else clicks! + if (isa->eg1_attack_0) { eg1_phase=1; eg1=eg_topval; } + else eg1_phase=0; + if (isa->eg2_attack_0) { eg2_phase=1; eg2=eg_topval; } + else eg2_phase=0; + if (isa->on_off[LFO_synced]) lfo_pos=0.; + } + ++note_on; + prev_note=mnr; + the_pitch=f; + osc1_waveform=isa->dete[VCO1_wf]; + osc1_pw=isa->cont[VCO1_pw]; + osc2_waveform=isa->dete[VCO2_wf]; + osc2_pw=isa->cont[VCO2_pw]; + osc_freq4=isa->cont[LFO_fr]/SAMPLE_RATE; + lfo_pw=isa->cont[LFO_pw]; + lfo_waveform=isa->dete[LFO_wf]; + lfo_amount_a=isa->cont[LFO_ampl]; + lfo_amount_o=isa->cont[LFO_pit]; + lfo_amount_f=isa->cont[LFO_filt]; + fm_amount=isa->cont[VCO1_fm]; + vco3_amount_a=isa->cont[VCO3_ma]; + vco3_amount_f=isa->cont[VCO3_filt]; + eg1_amount_o=isa->cont[EG1_lfo]; + eg2_amount_o=isa->cont[EG2_pit]; + qres=isa->cont[VCF_q]; + bal_o1=isa->cont[BAL_o1]; + bal_o2=isa->cont[BAL_o2]; + bal_ns=isa->cont[BAL_ns]; +} +/* +static float clip1(float in) { + if (in>0) + return in>2 ? 1 : in - in*in/4; + return in<-2 ? -2 : in + in*in/4; +} +*/ +static float clip2(float in) { + if (in>0) + return in>6 ? 3 : in - in*in/12; + return in<-6 ? -3 : in + in*in/12; +} + +void MSynth::calc_eg(float &eg,int &eg_phase,int EG_att,int EG_dec,int EG_sus,int EG_rel) { + switch (eg_phase) { + case 0: + eg+=isa->cont[EG_att]+(1.-eg)*isa->cont[EG_att]; + if (eg>eg_topval) { eg=eg_topval; eg_phase=1; } // flip from attack to decay + break; + case 1: + eg-=isa->cont[EG_dec]*(eg-isa->cont[EG_sus]); + if (eg-isa->cont[EG_sus] < eg_diffval) eg_phase=3; + break; + case 2: + if (eg>0.1) + eg-=isa->cont[EG_rel]*eg; + else + eg-=isa->cont[EG_rel]*0.1; + break; + case 3: + if (isa->cont[EG_sus]<0.05) + eg-=isa->cont[EG_rel]*0.1; + break; + } + if (eg<0.) eg=0.; +} + +bool MSynth::fill_buffer(float *buffer,const int buf_size,const float amp_mul) { + float lfo,freq_mod_2=1,osc1,osc2,osc3,input,output; + bool trig_vco1_2; + if (!note_on) { + eg1_phase=eg2_phase=2; + if (eg1 < 0.01) { // allow sound to complete its release phase + d1=d2=d3=d4=0.; // no sound, so reset filter delay storage + + if (note_on>0) --note_on; + return false; + } + } + for(int i=0; iis_porta) { + if (note_diff>0) { + fund_pitch /= isa->cont[PORT_tim]; + if (fund_pitch>the_pitch) { + fund_pitch=the_pitch; + note_diff=0; + } + } + else if (note_diff<0) { + fund_pitch *= isa->cont[PORT_tim]; + if (fund_pitchcont[VCF_coff]; + freqeg2=freq*isa->cont[EG2_filt]; + osc_freq1=isa->cont[VCO1_pit]*fund_pitch/SAMPLE_RATE; + osc_freq2=isa->cont[VCO2_pit]*fund_pitch/SAMPLE_RATE; + osc_freq3=isa->cont[VCO3_pit]*fund_pitch/SAMPLE_RATE; + + // EG1 section + calc_eg(eg1,eg1_phase,EG1_att,EG1_dec,EG1_sus,EG1_rel); + + // EG2 section + calc_eg(eg2,eg2_phase,EG2_att,EG2_dec,EG2_sus,EG2_rel); + } + // LFO section + lfo=oscillator(LFO_wf,lfo_pos,osc_freq4,lfo_waveform) * (1.+eg1_amount_o*(eg1-1.)); + + // VCO 1 section + osc1=oscillator(VCO1_wf,osc1_pos,osc_freq1,osc1_waveform,&trig_vco1_2); + if (trig_vco1_2 && isa->trig_val) osc2_pos=0; + + freq_mod_2=(1.+eg2*eg2_amount_o)*(1.+lfo*lfo_amount_o); // only VCO2 is modulated + // VCO 2 section + osc2=oscillator(VCO2_wf,osc2_pos, + isa->is_fm ? osc_freq2*freq_mod_2*(1+fm_amount*osc1) : osc_freq2*freq_mod_2, + osc2_waveform); + + // VCO 3 section + osc3=oscillator(0,osc3_pos,osc_freq3, osc3_waveform); + + float ns=get_noise(); + + // mixer section + input=bal_o1*osc1 + bal_o2*osc2 + bal_ns*ns; + if (isa->ringm_vco) input *= osc3; + else input *= 1. + osc3*vco3_amount_a; + if (isa->ringm_lfo) input *= lfo; + else input *= 1. + lfo*lfo_amount_a; + + // VCF section - Hal Chamberlin's state variable filter + // 20Hz - 6.7KHz + freqcut=fminmax(0.005,(freqkey+freqeg2*eg2)*(1. + lfo*lfo_amount_f + osc3*vco3_amount_f),FREQMAX); + + int fmo=isa->filter_mode; + if (isa->is_pole4) { // 24db per octave + float fc1=freqcut/1.2, // flatter peak + fc2=freqcut*1.2; + d2+=fc1*d1; // d2 = lowpass output (no clipping) + float highpass=input-d2-qres*d1; + d1+=fc1*highpass; // d1 = bandpass output + output= fmo==LOpass ? d2 : fmo==BANDpass ? d1 : highpass; + + if (isa->on_off[VCF_clip]) d4+=fc2*clip2(d3); // d4 = lowpass output + else d4+=fc2*d3; + highpass=output-d4-qres*d3; + d3+=fc2*highpass; // d3 = bandpass output (clipping here yields strange effects) + output= fmo==LOpass ? d4 : fmo==BANDpass ? d3 : highpass; + } + else { + d2+=freqcut*d1; // d2 = lowpass output (no clipping) + float highpass=input-d2-qres*d1; + d1+=freqcut*highpass; // d1 = bandpass output + output= fmo==LOpass ? d2 : fmo==BANDpass ? d1 : highpass; + } + buffer[i] += amp_mul * output * eg1 * isa->cont[VOL]; // update the buffer + } + vco1_val=osc_freq1; + vco2_val=osc_freq2*freq_mod_2; + cutoff_val=freqcut/freq; + return true; +} diff --git a/src/mono-synth.h b/src/mono-synth.h new file mode 100644 index 0000000..a7fa55b --- /dev/null +++ b/src/mono-synth.h @@ -0,0 +1,53 @@ +const int + NSLIDER=31, + NRBUT=5, + NCHECKB=3, + NCONTROLS=NSLIDER+NRBUT+NCHECKB; + +struct MSynthData { + int col; + uchar hsl_buf[NSLIDER], + rb_buf[NRBUT], + cb_buf[NCHECKB]; + bool fill_msynth_data(int col,const char *str); +}; + +struct Synth { + struct MSynth *m_synth; + uint topwin; // top window + XftDraw *xft_topwin; + int *ms_ampl; // pointer to amplitude value + const int col; + int isa_col; + float mult_eg; // used for eg display + SubWin *subw; + HSlider *hsliders[NSLIDER]; + RButWin *rbutwins[NRBUT]; + CheckBox *checkboxs[NCHECKB]; + Button *eq; + struct Patches* set_patch; + BgrWin *vcf_display, + *eg1_display, + *eg2_display, + *vco1_display, + *vco2_display, + *vco3_display, + *lfo_display; + Synth(Point top,int col,bool do_map); + void init(bool soft); + void set_values(int,float freq); + bool fill_buffer(float *buf,const int buf_size,const float amp_mul); + void note_off(); + void update_values(MSynthData*,bool setpatch); + void dump_settings(char *buf,int bufmax,const char *col); + void button_cmd(Id); + void set_isa(Synth *other); + void map_topwin(bool do_map); + void draw_isa_col(bool); + void draw_eg_display(int eg); + void draw_wave_display(int eg,bool test_if_needed=false); +}; + +extern bool debug; +extern const int nop; +void monosynth_uev(int cmd,int id); diff --git a/src/physical-mod.cpp b/src/physical-mod.cpp new file mode 100644 index 0000000..ec92e36 --- /dev/null +++ b/src/physical-mod.cpp @@ -0,0 +1,194 @@ +/* physical-mod.cpp + * Derived from api-wrapper.c (from PsIndustrializer), modified by W.Boeke + * Copyright (c) 2000 David A. Bartold + * Copyright (c) 2004 Yury G. Aliaev + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This library 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. + */ + +#include +#include +#include +#include +#include "amuc-headers.h" + +static const int sample_rate=44100; // same as for wave files + +PhmBuffer phm_buf; + +struct vector2 { + float x, z; +}; + +struct Node { + bool anchor; + vector2 pos; + vector2 vel; + Node *neighbors[2]; +}; + +struct Nodes { + int num_nodes; + Node *nodes; +}; + +void eval_model (Nodes *obj, float speed, float damp) { + int i, j; + vector2 sum, + dif; + Node *node; + float temp; + + for (i = 0; i < obj->num_nodes; i++) { + node = obj->nodes+i; + if (!node->anchor) { + sum.x = sum.z = 0.0; + + for (j = 0; j < 2; j++) { + dif.x = node->pos.x - node->neighbors[j]->pos.x; + dif.z = node->pos.z - node->neighbors[j]->pos.z; + temp = hypot(dif.x, dif.z); + sum.x -= dif.x * temp; + sum.z -= dif.z * temp; + } + node->vel.x = node->vel.x * damp + sum.x * speed; + node->vel.z = node->vel.z * damp + sum.z * speed; + } + } + + for (i = 0; i < obj->num_nodes; i++) { + node = obj->nodes+i; + + if (!node->anchor) { + node->pos.x += node->vel.x * speed; + node->pos.z += node->vel.z * speed; + } + } +} + +static float non_linear(float x) { + const float limit=0.5; + if (x>0.) { + if (x>limit) return limit; + return 2*(x-x*x/2/limit); + } + else { + if (x<-limit) return -limit; + return 2*(x+x*x/2/limit); + } +} + +uint obj_render(Nodes * obj, int outnode, float speed, + float damp_time, int maxlen, float *samples, bool add_dist, bool timing_only) { + int i, + len; + float sample, + damp; + Node *out=obj->nodes+outnode; + const float stasis = hypot(out->pos.x,out->pos.z); + + len = min(lround(damp_time * sample_rate * 6.0), maxlen); // good value for big damp_time value + if (timing_only) + return len; + damp = pow(0.5, 1.0 / damp_time / sample_rate); + + if (debug) + printf("obj_render: speed=%.2f damp=%.5f len=%d\n",speed,damp,len); + for (i = 0; i < len; i++) { + eval_model(obj, speed, damp); + sample = hypot(out->pos.x, out->pos.z) - stasis; + if (i > len-100) sample*=(len-i)/100.; + if (add_dist) // distorsion added + samples[i] = non_linear(sample); + else + samples[i] = sample; + } + return len; +} + +uint obj_render_rod(int height, float tension, float speed, + float damp, int len, float *samples, bool add_dist, bool timing_only) { + static Nodes *obj; + Node *node; + int i; + if (!obj) { + obj = new Nodes; + obj->nodes=new Node[height]; + obj->num_nodes = height; + for (i = 0; i < height; i++) { + node = obj->nodes+i; + + if (i == 0 || i == height - 1) { + node->neighbors[0] = node->neighbors[1] = 0; + node->anchor = true; + } + else { + node->neighbors[0] = obj->nodes+(i - 1); + node->neighbors[1] = obj->nodes+(i + 1); + node->anchor = false; + } + } + } + for (i = 0; i < height; i++) { + node = obj->nodes+i; + node->pos.x = 0.; + node->pos.z = i * tension; // empirically, this sounds best + node->vel.x=node->vel.z=0.; + } + obj->nodes[1].pos.x = 2.0; // input node + + return obj_render(obj, height-2, speed, damp, len, samples, add_dist, timing_only); +} + +static void set_phm_data(ShortBuffer *pmd,PhmCtrl *ctrl,bool timing_only,const int ampl_scale) { + static float + tension[6] = { 0, 0.3, 0.5, 1, 1.5, 2 }, // all indexes: 1 - 5 + speed[6] = { 0, 0.015,0.02, 0.03, 0.04, 0.05}, + damp[6] = { 0, 0.01, 0.02, 0.04, 0.06, 0.1 }; + const int max_data_len=sample_rate; // enough for 1 sec + float data[max_data_len]; + pmd->size=obj_render_rod( + 6, + tension[ctrl->speed_tension->value.y], + speed[ctrl->speed_tension->value.x], + damp[ctrl->decay->value()], + max_data_len,data,ctrl->add_noise->value,timing_only); + if (timing_only) return; + for (int i=0;isize;++i) + pmd->buf[i]=minmax(-30000,int(data[i]*ampl_scale),30000); +} + +bool PhmBuffer::set_phys_model(int col,ShortBuffer *pmdat) { + ShortBuffer *pmd=pmdat ? pmdat : const_data + col; + var_data[col]=pmd; + pmd->reset(); + + set_phm_data(pmd,col2phm_ctrl(col),true,0); // sets pmd->size + pmd->buf=new short[pmd->size]; + pmd->alloced=true; + for (int i=0;isize;++i) pmd->buf[i]=0; + + set_phm_data(pmd,col2phm_ctrl(col),false,ampl_scale); + return true; +} + +bool PhmBuffer::init(const int ampl) { + ampl_scale=ampl; + for (int n = 0; n +#include +#include "amuc-headers.h" + +PostscriptOut ps_out; + +static const int + subv_max=3, + voice_len_max=5000; // max chars + +static char + program[colors_max*2*voice_len_max], + *prog, + buf1[10],buf2[10]; + +struct SubVoice { + char buf[voice_len_max], *bp; + int busy, + prev_mnr; // previous non-rest measure nr + void reset() { + buf[0]=0; + bp=buf; + busy=0; + prev_mnr=0; + } +}; + +struct Voice { + SubVoice sv[subv_max]; + uchar local_lsign[sclin_max]; + int voice_key_nr; + bool chord_warn; // if s_voice: warning about chord given? + void reset() { + chord_warn=false; + for (int i=0;ips_out.meter) { + next_dur=dur - ps_out.meter + time; + dur-=next_dur; + } + extra_dur= time%subd && time%subd+dur>subd ? dur - subd + time%subd : 0; + } + PsNote(int c,int ng,int mn,int t,int d): // sampled note + mnr(mn), + time(t), + col(c), + notegr(ng), + dur(d), + extra_dur(0), + sampled(true), + tied(0) { + } + + bool operator<(PsNote &other) { return time < other.time; } + bool operator==(PsNote &other) { return false; } // always insert + const char* abc_note_name(); + const char* abc_perc_note_name(); +}; + +const char *PsNote::abc_note_name() { + // abc note name: midi_nr=60 -> abc note = 'c' + static const char *name_hi[84]={ + "C,,,","^C,,,","D,,,","^D,,,","E,,,","F,,,","^F,,,","G,,,","^G,,,","A,,,","^A,,,","B,,,", + "C,," ,"^C,," ,"D,," ,"^D,," ,"E,," ,"F,," ,"^F,," ,"G,," ,"^G,," ,"A,," ,"^A,," ,"B,,", + "C," ,"^C," ,"D," ,"^D," ,"E," ,"F," ,"^F," ,"G," ,"^G," ,"A," ,"^A," ,"B,", + "C" ,"^C" ,"D" ,"^D" ,"E" ,"F" ,"^F" ,"G" ,"^G" ,"A" ,"^A" ,"B", + "c" ,"^c" ,"d" ,"^d" ,"e" ,"f" ,"^f" ,"g" ,"^g" ,"a" ,"^a" ,"b", + "c'" ,"^c'" ,"d'" ,"^d'" ,"e'" ,"f'" ,"^f'" ,"g'" ,"^g'" ,"a'" ,"^a'" ,"b'", + "c''" ,"^c''" ,"d''" ,"^d''" ,"e''" ,"f''" ,"^f''" ,"g''" ,"^g''" ,"a''" ,"^a''" ,"b''" }; + static const char *name_lo[84]={ + "C,,,","_D,,,","D,,,","_E,,,","E,,,","F,,,","_G,,,","G,,,","_A,,,","A,,,","_B,,,","B,,,", + "C,," ,"_D,," ,"D,," ,"_E,," ,"E,," ,"F,," ,"_G,," ,"G,," ,"_A,," ,"A,," ,"_B,," ,"B,,", + "C," ,"_D," ,"D," ,"_E," ,"E," ,"F," ,"_G," ,"G," ,"_A," ,"A," ,"_B," ,"B,", + "C" ,"_D" ,"D" ,"_E" ,"E" ,"F" ,"_G" ,"G" ,"_A" ,"A" ,"_B" ,"B", + "c" ,"_d" ,"d" ,"_e" ,"e" ,"f" ,"_g" ,"g" ,"_a" ,"a" ,"_b" ,"b", + "c'" ,"_d'" ,"d'" ,"_e'" ,"e'" ,"f'" ,"_g'" ,"g'" ,"_a'" ,"a'" ,"_b'" ,"b'", + "c''" ,"_d''" ,"d''" ,"_e''" ,"e''" ,"f''" ,"_g''" ,"g''" ,"_a''" ,"a''" ,"_b''" ,"b''" }; + static const char *eq_name[84]={ + "=C,,,",0,"=D,,,",0,"=E,,,","=F,,,",0,"=G,,,",0,"=A,,,",0,"=B,,,", + "=C,," ,0,"=D,," ,0,"=E,," ,"=F,," ,0,"=G,," ,0,"=A,," ,0,"=B,,", + "=C," ,0,"=D," ,0,"=E," ,"=F," ,0,"=G," ,0,"=A," ,0,"=B,", + "=C" ,0,"=D" ,0,"=E" ,"=F" ,0,"=G" ,0,"=A" ,0,"=B", + "=c" ,0,"=d" ,0,"=e" ,"=f" ,0,"=g" ,0,"=a" ,0,"=b", + "=c'" ,0,"=d'" ,0,"=e'" ,"=f'" ,0,"=g'" ,0,"=a'" ,0,"=b'", + "=c''" ,0,"=d''" ,0,"=e''" ,"=f''" ,0,"=g''" ,0,"=a''" ,0,"=b''" }; + int ind; + bool eq=false; + uchar line_sign=voices[notegr][col].local_lsign[lnr]; + if (line_sign==note_sign) + ind=lnr_to_midinr(lnr,0) - 24; + else if (line_sign && !note_sign) { + ind=lnr_to_midinr(lnr,0) - 24; + eq=true; + } + else + ind=lnr_to_midinr(lnr,note_sign) - 24; + if (ind<0) { alert("abc_note_nname: note too low"); return 0; } + if (ind>=84) { alert("abc_note_nname: note too high"); return 0; } + if (debug) printf("abc_nname: ind:%d lsign:%d nsign:%d eq:%d nhi:%s nlo:%s neq:%s\n", + ind,line_sign,note_sign,eq,name_hi[ind],name_lo[ind],eq_name[ind]); + if (eq) return eq_name[ind]; + switch (note_sign) { + case eHi: + case 0: return name_hi[ind]; + case eLo: return name_lo[ind]; + default: return 0; + } +} + +const char *PsNote::abc_perc_note_name() { + static const char *name[colors_max]={ "C","E","G","B","^d","^f" }; + return name[col]; +} + +Array,1000> psn_buf; // max 1000 measures + +int transp_key(int key_nr,int tr) { // notice: key_names[6] and key_names[7] are equivalent + if (!tr) return key_nr; + int ind=0; + const int max1=keys_max-1; + if (tr<0) tr=-(-tr%12); + else tr=tr%12; + if (key_nr>5) --key_nr; + ind=key_nr+tr; + if (ind<0) ind+=max1; + else if (ind>=max1) ind-=max1; + if (ind>5) ++ind; + return ind; +} + +void PostscriptOut::set(int m,int nu,int knr,char *t) { + meter=m; + nupq=nu_subd=nu; + lst_ind=-1; + key_nr=knr % keys_max; // only major keys + title=t; + for (int i=0;i %d",mnr,old_dur,dur); + break; + } + if (dur==good_len[i]) break; + } +} + +void PostscriptOut::insert(int col,int grn,int ev_time,int lnr,int note_sign,int dur) { + int ind=ev_time/meter; + if (ind>lst_ind) + lst_ind=ind; + PsNote note(col,grn,ind,ev_time % meter,lnr,note_sign,dur); + if (debug) printf("insert ps-note: tim=%d dur=%d\n",ev_time % meter,dur); + PsNote *np=&psn_buf[ind].insert(note,true)->d; + if (np->extra_dur>0) test_dur(ind,np->extra_dur); + else test_dur(ind,np->dur); + while (np->next_dur>0) { + ++ind; + PsNote note1(col,grn,ind,0,lnr,note_sign,np->next_dur); + test_dur(ind,note1.dur); + np->tied=&psn_buf[ind].prepend(note1)->d; + if (ind>lst_ind) lst_ind=ind; + np=¬e1; + if (np->extra_dur>0) test_dur(ind,np->extra_dur); + else test_dur(ind,np->dur); + } +} + +void PostscriptOut::insert_perc(int col,int grn,int ev_time) { + int ind=ev_time/meter; + if (ind>lst_ind) + lst_ind=ind; + PsNote perc_note(col,grn,ind,ev_time % meter,nupq/2); + if (debug) printf("insert ps-perc-note: tim=%d\n",ev_time % meter); + psn_buf[ind].insert(perc_note,true); +} + +static char *n_len(char *buf,int dur) { // abc note length code + if (dur==1) strcpy(buf,""); + else sprintf(buf,"%d",dur); + return buf; +} + +static char *r_len(char *buf,int mnr,int dur,const char *r) { // abc rest code + for (int i=0;;++i) { + if (good_len[i] *psn,SLList_elem *psn1) { + if (psn1->d.sampled != psn->d.sampled) return false; + return psn1->d.col==psn->d.col && psn1->d.notegr==psn->d.notegr && psn1->d.time==psn->d.time && psn1->d.dur==psn->d.dur; +} + +static char *add_rest(int mnr,int time,char *bp,const char *rest,int len) { + int subd=ps_out.nu_subd, + extra_len= time%subd && time%subd+len>subd ? len - subd + time%subd : 0; + if (time>0 && time%4==0) strcpy(bp++," "); // interrupt note beaming + if (extra_len) { + bp += sprintf(bp,"%s",r_len(buf1,mnr,len-extra_len,rest)); + if ((time+len-extra_len)%4==0) strcpy(bp++," "); + bp += sprintf(bp,"%s",r_len(buf2,mnr,extra_len,rest)); + } + else + bp += sprintf(bp,"%s",r_len(buf1,mnr,len,rest)); + return bp; +} + +bool PostscriptOut::find_ai(int mnr,int& ai) { + for (ai=0;ai=0;++ai) + if (mnr==annot[ai].mnr) return true; + return false; +} + +void PostscriptOut::add_Z_rest(Voice *voice,int subv_nr,int meas_nr) { + SubVoice *subv=voice->sv+subv_nr; + if (meas_nr-subv->prev_mnr > 0) { + int mnr,d_mnr,ai; + for (mnr=subv->prev_mnr;mnrprev_mnr; + if (d_mnr==1) + subv->bp += sprintf(subv->bp,"z%d|",meter); + else if (d_mnr>1) // could be 0 + subv->bp += sprintf(subv->bp,"Z%d|",d_mnr); + subv->bp += sprintf(subv->bp,"\"^%c\"",annot[ai].ch); + subv->prev_mnr=mnr; + } + } + d_mnr=meas_nr-subv->prev_mnr; + if (d_mnr==1) + subv->bp += sprintf(subv->bp,"z%d|",meter); + else + subv->bp += sprintf(subv->bp,"Z%d|",d_mnr); + } + subv->prev_mnr=meas_nr; +} + +void PostscriptOut::add_first_rests(Voice *voice,int subv_nr,int meas_nr,const char* rest) { + int i,ai=0; + SubVoice *subv=voice->sv+subv_nr; + for (i=0;i< meas_nr;++i) { + if (subv_nr==0 && i==annot[ai].mnr) { + subv->bp += sprintf(subv->bp,"\"^%c\"",annot[ai].ch); + ++ai; + } + subv->bp += sprintf(subv->bp,"%s%d|",rest,meter); + } +} + +void PostscriptOut::add_last_rest(Voice *voice,int subv_nr,int meas_nr,const char* rest,int annot_ch) { + SubVoice *subv=voice->sv+subv_nr; + if (subv->buf[0]) { + if (subv->busybusy==0) + subv->bp += sprintf(subv->bp,"\"^%c\"",annot_ch); + subv->bp=add_rest(meas_nr,subv->busy,subv->bp,rest,meter - subv->busy); + } + subv->bp=stpcpy(subv->bp,"|"); + } +} + +bool PostscriptOut::fill_subvoice(const int meas_nr,int subv_nr,int annot_ch,bool& done) { + SLList_elem *psn,*psn1; + Voice *voice; + SubVoice *subv; + const char *rest= subv_nr==0 || s_voice ? "z" : "x"; + if (debug) printf("fsub meas=%d subv_nr=%d\n",meas_nr,subv_nr); + for (int c=0;cd.sampled) { + psn=psn->nxt; continue; + } + done=false; + bool chord=false; + voice=voices[psn->d.notegr]+psn->d.col; + subv=voice->sv+subv_nr; + if (debug) printf("meas_nr=%d tim=%d dur=%d busy=%d col=%s tied=%p pref_subv=%d\n", + meas_nr,psn->d.time,psn->d.dur,subv->busy,color_name[psn->d.col],psn->d.tied,psn->d.pref_subv); + if (psn->d.timebusy || + (psn->d.pref_subv >=0 && psn->d.pref_subv != subv_nr)) { // test preferred subvoice nr + if (debug) puts("skip"); + psn=psn->nxt; + continue; + } + if (!subv->buf[0] && !s_voice) + add_first_rests(voice,subv_nr,meas_nr,rest); + if (s_voice && subv_nr>0) + alert("warning: multiple voice in measure %d (color:%s group:%d)", + meas_nr,color_name[psn->d.col],psn->d.notegr); + if ((subv_nr==0 || s_voice) && annot_ch && subv->busy==0) { + if (s_voice) add_Z_rest(voice,subv_nr,meas_nr); + subv->bp += sprintf(subv->bp,"\"^%c\"",annot_ch); + } + else if (s_voice) + add_Z_rest(voice,subv_nr,meas_nr); // now: prev_mnr = meas_nr + if (subv->busy < psn->d.time) + subv->bp=add_rest(psn->d.mnr,subv->busy,subv->bp,rest,psn->d.time - subv->busy); + if (psn->d.time>0 && psn->d.time%4==0) // interrupt note beaming + strcpy(subv->bp++," "); + subv->busy=psn->d.time + psn->d.dur; + for (psn1=psn->nxt;psn1 && psn1->d.time < subv->busy;psn1=psn1->nxt) { + if (psn1->d.sampled) + continue; + if (is_chord(psn,psn1)) { + chord=true; + if (s_voice && !voice->chord_warn) { + voice->chord_warn=true; + alert("warning: chord in measure %d (color:%s group:%d)",meas_nr,color_name[psn->d.col],psn->d.notegr); + } + subv->bp=stpcpy(subv->bp,"["); + break; + } + } + if (subv->bp - subv->buf > voice_len_max-100) { + alert("fill_subvoice: buffer overflow"); + return false; + } + //if (psn->d.fst_of_triplet) subv->bp=stpcpy(subv->bp,"(3"); + const char *nn1=psn->d.abc_note_name(); + voices[psn->d.notegr][psn->d.col].local_lsign[psn->d.lnr]=psn->d.note_sign; + if (debug) printf("time=%d, abc nname:%s\n",psn->d.time,nn1); + if (psn->d.extra_dur) { + if (chord) // too difficult! + subv->bp += sprintf(subv->bp,"%s%s", nn1, n_len(buf1,psn->d.dur)); + else subv->bp += sprintf(subv->bp,"%s%s-%s%s", + nn1,n_len(buf1,psn->d.dur-psn->d.extra_dur), + psn->d.abc_note_name(), // maybe different name + n_len(buf2,psn->d.extra_dur)); + } + else + subv->bp += sprintf(subv->bp,"%s%s", nn1, n_len(buf1,psn->d.dur)); + if (psn->d.tied) { + psn->d.tied->pref_subv=subv_nr; + strcpy(subv->bp++,"-"); + } + if (chord) { + for (psn1=psn->nxt;psn1 && psn1->d.time < subv->busy;) + if (is_chord(psn,psn1)) { + nn1=psn1->d.abc_note_name(); + subv->bp += sprintf(subv->bp,"%s%s",nn1,n_len(buf1,psn->d.dur)); + psn1=psn_buf[meas_nr].remove(psn1); + } + else psn1=psn1->nxt; + subv->bp=stpcpy(subv->bp,"]"); + } + psn=psn_buf[meas_nr].remove(psn); + } + for (int i=0;isv+subv_nr; + if (s_voice) { + if (subv->busy) { + add_last_rest(voice,subv_nr,meas_nr,rest,annot_ch); + ++subv->prev_mnr; + } + } + else { + add_last_rest(voice,subv_nr,meas_nr,rest,annot_ch); + } + } + if (debug) puts("---"); + return true; +} + +bool PostscriptOut::fill_perc_subv(const int meas_nr,int subv_nr,int annot_ch,bool& done) { + SLList_elem *psn,*psn1; + Voice *voice; + SubVoice *subv; + const char *rest= subv_nr==0 || s_voice ? "z" : "x"; + done=true; + for (psn=psn_buf[meas_nr].lis;psn;) { + if (!psn->d.sampled) { + psn=psn->nxt; continue; + } + done=false; + bool chord=false; + voice=perc_voices+psn->d.notegr; + subv=voice->sv+subv_nr; + if (psn->d.timebusy) { + if (debug) puts("skip"); + psn=psn->nxt; + continue; + } + if (!subv->buf[0] && !s_voice) + add_first_rests(voice,subv_nr,meas_nr,rest); + if ((subv_nr==0 || s_voice) && annot_ch && subv->busy==0) { + if (s_voice) add_Z_rest(voice,subv_nr,meas_nr); + subv->bp += sprintf(subv->bp,"\"^%c\"",annot_ch); + } + else + if (s_voice) add_Z_rest(voice,subv_nr,meas_nr); + if (subv->busy < psn->d.time) + subv->bp=add_rest(meas_nr,subv->busy,subv->bp,rest,psn->d.time - subv->busy); + subv->busy=psn->d.time + psn->d.dur; + if (psn->d.time>0 && psn->d.time%4==0) // interrupt percussion-note beaming + strcpy(subv->bp++," "); + for (psn1=psn->nxt;psn1 && psn1->d.time < subv->busy;psn1=psn1->nxt) { + if (!psn1->d.sampled) + continue; + chord=true; + subv->bp=stpcpy(subv->bp,"["); + break; + } + if (subv->bp - subv->buf > voice_len_max-100) { + alert("fill_perc_subv: buffer overflow"); + return false; + } + const char *nn=psn->d.abc_perc_note_name(); + subv->bp += sprintf(subv->bp,"%s%s", nn, n_len(buf1,psn->d.dur)); + if (chord) { + for (psn1=psn->nxt;psn1 && psn1->d.time < subv->busy;) { + nn=psn1->d.abc_perc_note_name(); + subv->bp += sprintf(subv->bp,"%s%s",nn,n_len(buf1,psn->d.dur)); + psn1=psn_buf[meas_nr].remove(psn1); + } + subv->bp=stpcpy(subv->bp,"]"); + } + psn=psn_buf[meas_nr].remove(psn); + } + for (int n=0;nsv+subv_nr; + if (s_voice) { + if (subv->busy) { + add_last_rest(voice,subv_nr,meas_nr,rest,annot_ch); + ++subv->prev_mnr; + } + } + else { + add_last_rest(voice,subv_nr,meas_nr,rest,annot_ch); + } + } + return true; +} + +static int calc_svnr(int gr_nr,int voice_nr,int subv_nr) { + return subv_max*(gr_nr*colors_max + voice_nr) + subv_nr + 1; +} + +static int calc_perc_svnr(int gr_nr,int subv_nr) { + return subv_max*gr_nr + subv_nr + 1; +} + +void PostscriptOut::print_subvoice(int voice_nr,int group_nr,Voice *voice,int subv) { + if (!voice->sv[subv].buf[0]) return; + prog += sprintf(prog,"V:%d\n",calc_svnr(group_nr,voice_nr,subv)); + if (subv==0) + prog += sprintf(prog,"[K:%s]",maj_min_keys[voice->voice_key_nr]); + prog += sprintf(prog,"%s|\n",voice->sv[subv].buf); +} + +void PostscriptOut::print_perc_subvoice(int group_nr,Voice *voice,int subv) { + if (!voice->sv[subv].buf[0]) return; + prog += sprintf(prog,"V:P%d\n",calc_perc_svnr(group_nr,subv)); + prog += sprintf(prog,"%s|\n",voice->sv[subv].buf); +} + +void PostscriptOut::write_ps(bool abc_only) { + int meas_nr, + subv_nr, + v_nr, + gr_nr, + annot_char, + a_ind=0; + bool done; + SubVoice *subv; + for (int c=0;cbuf[0]) + printf(" name:%-2d color:%-6s group:%d\n", + calc_svnr(gr_nr,v_nr,0),color_name[v_nr],gr_nr); + } + for (gr_nr=0;gr_nrbuf[0]) + printf(" name:P%d (percussion) group:%d\n",calc_perc_svnr(gr_nr,0),gr_nr); + } + } + else { + prog += sprintf(prog,"X:1\n"); // start + prog += sprintf(prog,"T:%s\n",title); // title + prog += sprintf(prog,"M:%d/4\n",meter*4/nupq/4); // meter + prog += sprintf(prog,"L:1/%d\n",4*nupq); // unit note length + prog += sprintf(prog,"K:%s\n",maj_min_keys[key_nr]); // key + prog += sprintf(prog,"%%%%staves ["); // stave numbers + for (v_nr=0;v_nrbuf[0]) { + if (voices[gr_nr][v_nr].sv[1].buf[0]) { + prog=stpcpy(prog,"("); + for (int i=0;ibuf[0]) { + if (perc_voices[gr_nr].sv[1].buf[0]) { + prog=stpcpy(prog,"("); + for (int i=0;ibuf[0],calc_svnr(gr_nr,v_nr,0)); + if (subv->buf[0]) { + prog += sprintf(prog,"V:%d\tnm=\"%s-%d\"\tsnm=\"%s-%d\"\tclef=treble\n", + calc_svnr(gr_nr,v_nr,0),color_name[v_nr],gr_nr,color_name[v_nr],gr_nr); + } + } + for (gr_nr=0;gr_nrbuf[0],calc_perc_svnr(gr_nr,0)); + if (subv->buf[0]) { + prog += sprintf(prog,"V:P%d\tnm=\"perc-%d\"\tsnm=\"perc-%d\"\tclef=perc\n", + calc_perc_svnr(gr_nr,0),gr_nr,gr_nr); + } + } + } + for (v_nr=0;v_nr +#include +//#include +#include +#include +#include + +#include "amuc-headers.h" + +WaveBuffer wave_buf; +static const int sample_rate=44100; + +void WaveBuffer::init() { // must be called after start of X, because colours are used + fname_nr=-1; + filenames=new FileName[wavef_max]; + sample_dirs=new SampleDirs(); +} + +void WaveBuffer::reset_buffers() { // non-alloc'ed buf's are pointing to ScInfo::wavedata, which maybe has been deleted + for (int i=0;i %d directories",dirs_max); + return; + } + ++dirs_end; + dirs[dirs_end].name=dname; + dirs[dirs_end].col=dircol; +} + +bool SampleDirs::coll_wavefiles() { + bool res=false; + wave_buf.fname_nr=-1; + for (int i=0;i<=dirs_end;++i) { + if (wave_buf.coll_wavefiles(dirs[i].name,dirs[i].col)) res=true; + } + if (!res) alert("no wave files found"); + return res; +} + +static int compar_fn(const void* a,const void* b) { + return reinterpret_cast(a)->nr > reinterpret_cast(b)->nr; +} + +bool WaveBuffer::fill_fname_array(const char *data_dir,const char *fnames[],int *fnumbers) { + int nr; + DIR *dp; + dirent *dir; + bool file_found=false; + char *ext; + if (!(dp=opendir(data_dir))) { + alert("dir %s not accessable",data_dir); + return false; + } + while ((dir=readdir(dp))!=0) { + if (!dir->d_ino) { alert("d_ino?"); continue; } + ext=strrchr(dir->d_name,'.'); + if (!ext || strcmp(ext,".wav")) continue; + if (isdigit(dir->d_name[0])) { + nr=atoi(dir->d_name); + file_found=true; + if (fname_nr==wavef_max-1) { alert("files > %d",wavef_max); return false; } + ++fname_nr; + fnames[fname_nr]=strdup(dir->d_name); + fnumbers[fname_nr]=nr; + if (debug) printf("f_fn_arr: %s %d\n",fnames[fname_nr],nr); + } + } + closedir(dp); + return file_found; +} + +bool WaveBuffer::coll_wavefiles(const char *data_dir,XftColor *dircol) { + const char *fnames[wavef_max]; + int fnumbers[wavef_max]; + int fst_fname_nr=fname_nr+1; // starts at 0 + const char *dir=strdup(data_dir); + if (fill_fname_array(data_dir,fnames,fnumbers)) { + for (int i=fst_fname_nr;i<=fname_nr;++i) { + FileName *fn=filenames+i; + fn->name=fnames[i]; + fn->nr=fnumbers[i]; + fn->dir=dir; + fn->col=dircol; + } + qsort(filenames+fst_fname_nr,fname_nr+1-fst_fname_nr,sizeof(FileName),compar_fn); + return true; + } + return false; +} + +bool read_wav(FILE *src,ShortBuffer *sb) { + char word[20]; + short dum16; + int dum32, + size, + er_nr=0; + if ( + (fread(word,4,1,src)!=1 && (er_nr=1)) || (strncmp(word,"RIFF",4) && (er_nr=2)) || + (fread(&dum32, 4,1,src)!=1 && (er_nr=3)) || // header size + (fread(word, 8,1,src)!=1 && (er_nr=4)) || (strncmp(word,"WAVEfmt ",8) && (er_nr=5)) || + (fread(&dum32, 4,1,src)!=1 && (er_nr=6)) || (dum32!=16 && (er_nr=7)) || // chunk size + (fread(&dum16, 2,1,src)!=1 && (er_nr=8)) // format tag (1 = uncompressed PCM) + ) goto error_rw; // { printf("erno=%d du=%d\n",er_nr,dum32); goto error_rw; } + + if (dum16!=1) { alert("format = %d, should be 1",dum16); return false; } + if (fread(&dum16, 2,1,src)!=1) { // no of channels + er_nr=11; goto error_rw; + } + if (dum16!=1) { alert("nr channels = %d, should be 1",dum16); return false; } + if (fread(&dum32, 4,1,src)!=1) { // rate + er_nr=13; goto error_rw; + } + if (dum32!=sample_rate) { alert("rate = %d, must be %d",dum32,sample_rate); return false; } + if (fread(&dum32, 4,1,src)!=1) { // average bytes/sec + er_nr=15; goto error_rw; + } + if (dum32!=sample_rate*2) { alert("byte/sec = %d, must be 2*%d",dum32,sample_rate); return false; } + if ((fread(&dum16, 2,1,src)!=1 && (er_nr=16)) || // block align + (fread(&dum16, 2,1,src)!=1 && (er_nr=17))) // bits per sample + goto error_rw; + if (dum16!=16) { + alert("bits per sample is %d, must be 16",dum16); return false; + } + if ((fread(word, 4,1,src)!=1 && (er_nr=18)) || (strncmp(word,"data",4) && (er_nr=19))) + goto error_rw; + if (fread(&size, 4,1,src)!=1 && (er_nr=20)) // sample length + goto error_rw; + + error_rw: // this label must be before allocation of buf, else g++ will complain + if (er_nr) { + alert("format error (nr %d) in wave file",er_nr); + return false; + } + char *buf=new char[size]; + if (fread(buf,size,1,src)!=1 && (er_nr=21)) { + delete[] buf; + goto error_rw; + } + sb->reset(); + sb->size=size/2; // sizeof(char)/sizeof(short) = 2 + sb->buf=new short[sb->size]; + if (debug) printf("wave size=%d\n",sb->size); + sb->alloced=true; + for (int n=0;nsize;++n) + sb->buf[n]=*(short*)(buf+2*n); + delete[] buf; + return true; +} + +bool read_wav(const char *dir,const char *file,ShortBuffer *sb) { + FILE *src=0; + if (dir) { + char path[max200]; + snprintf(path,max200,"%s/%s",dir,file); + path[max200-1]=0; + src=fopen(path,"r"); + if (!src) { + alert("file '%s' not found",path); return false; + } + } + else { + src=fopen(file,"r"); + if (!src) { + alert("file '%s' not found",file); return false; + } + } + bool res=read_wav(src,sb); + fclose(src); + return res; +} + diff --git a/src/read-waves.h b/src/read-waves.h new file mode 100644 index 0000000..673bebd --- /dev/null +++ b/src/read-waves.h @@ -0,0 +1,37 @@ +struct FileName { + int nr; + XftColor *col; + const char *name; + const char *dir; +}; + +struct DirName { + XftColor *col; + const char *name; +}; + +struct SampleDirs { + static const int dirs_max=3; + int dirs_end; + DirName dirs[dirs_max]; + SampleDirs(); + void reset(); + void add_dir(const char *dname,XftColor *dircol); + bool coll_wavefiles(); +}; + +struct WaveBuffer { + int fname_nr; + ShortBuffer w_buf[colors_max]; // modified at runtime + FileName *filenames; + SampleDirs *sample_dirs; + void init(); + void reset_buffers(); + bool coll_wavefiles(const char *dir,XftColor *dircol); + bool fill_fname_array(const char *data_dir,const char *fnames[],int *fnumbers); +}; + +bool read_wav(const char *dir,const char *file,ShortBuffer*); + +extern WaveBuffer wave_buf; +extern const int wavef_max; diff --git a/src/snd-interface.cpp b/src/snd-interface.cpp new file mode 100644 index 0000000..f9c3e61 --- /dev/null +++ b/src/snd-interface.cpp @@ -0,0 +1,193 @@ +#include +#include +#include "snd-interface.h" +#include "colors.h" + +void alert(const char *form,...); // from x-widgets.h +void send_uev(int cmd,int param1=0,int param2=0); + +int + SAMPLE_RATE=44100, + IBsize=1024; // sound buffers, may be modified if output_port = eJack + +bool no_jack_conn; + +snd_pcm_t *pcm_handle; // global, only one instance of SndInterf can be active +snd_pcm_hw_params_t *hwparams; +const char *pcm_dev_name="default"; + +typedef jack_default_audio_sample_t sample_t; + +static uint + nom_rate = 44100, // Sample rate + exact_rate, // Sample rate returned by snd_pcm_hw_params_set_rate_near + periods = 8; // Number of periods + +SndInterf::SndInterf(): + periodsize(1024*8/periods), + okay(false) { + snd_pcm_hw_params_alloca(&hwparams); + if (snd_pcm_open(&pcm_handle, pcm_dev_name, SND_PCM_STREAM_PLAYBACK, 0) < 0) { + alert("Error opening PCM device %s (maybe it's busy)", pcm_dev_name); return; + } + +//Simple: yields xruns +// if (snd_pcm_set_params(pcm_handle, +// SND_PCM_FORMAT_S16_LE, +// SND_PCM_ACCESS_RW_INTERLEAVED, +// 2, +// 44100, +// 1, +// periodsize * periods / 4) < 0) { +// alert("Can not configure this PCM device"); +// return; +// } +// okay=true; return; + + if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) { + alert("Can not configure this PCM device."); return; + } + if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + alert("Error setting access."); return; + } + if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) { + alert("Error setting format."); return; + } + exact_rate = nom_rate; + if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) { + alert("Error setting rate."); return; + } + if (nom_rate != exact_rate) { + alert("The rate %d Hz is not supported, using %d Hz instead.", nom_rate, exact_rate); + } + if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) { + alert("Error setting channels"); return; + } + if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) { + alert("Error setting periods"); return; + } + // Set buffer size (in frames). The resulting latency is given by: + // latency = periodsize * periods / (rate * bytes_per_frame) + snd_pcm_uframes_t buffer_frames=periodsize * periods / 4; + if (snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams,&buffer_frames) < 0) { + alert("Error setting buffersize"); return; + } + //printf("buffer_frames=%u\n",buffer_frames); + IBsize=buffer_frames/4; + periodsize=buffer_frames; + if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) { + alert("Error setting HW params"); return; + } + okay=true; +} + +SndInterf::~SndInterf() { + if (okay && snd_pcm_close(pcm_handle) < 0) + alert("Error closing PCM device %s", pcm_dev_name); +} + +void SndInterf::snd_write(short *buffer) { + if (snd_pcm_writei(pcm_handle, buffer, periodsize/4) < 0) { + snd_pcm_prepare(pcm_handle); + puts("buffer not full"); // no warning + } +} + +struct Local { + jack_client_t *client; + jack_port_t *output_port_left, + *output_port_right; + jack_status_t status; +}; + +int process (jack_nframes_t nframes, void *arg) { + JackInterf *own=reinterpret_cast(arg); +//printf("is_play? %d\n",own->is_playing); fflush(stdout); + sample_t *buffer_left=(sample_t*)jack_port_get_buffer(own->d->output_port_left,nframes), + *buffer_right=(sample_t*)jack_port_get_buffer(own->d->output_port_right,nframes); + if (own->is_playing) { + if (!own->play(buffer_left,buffer_right)) + send_uev(own->done); + } + else + for (uint i=0;iclient); + delete d; +} + +JackInterf::JackInterf(const char *_client_name,bool (*_play)(float *buf_left,float *buf_right),int _done,bool &is_p): + client_name(_client_name), + okay(false), + is_playing(is_p), + play(_play), + done(_done), + buf_size(0), + sample_rate(0), + d(new Local()) { + if ((d->client = jack_client_open (client_name, JackNoStartServer, &d->status)) == 0) { + alert("Jack server not running?"); + return; + } + int sr=jack_get_sample_rate(d->client); + if (sr!=SAMPLE_RATE) { + printf("Sample rate %d set to %d\n",SAMPLE_RATE,sr); sample_rate=sr; + } + int bs=jack_get_buffer_size(d->client); + if (bs!=IBsize) { + printf("Buffer size %d set to %d\n",IBsize,bs); buf_size=bs; + } + jack_set_process_callback(d->client, process,this); + jack_set_error_function(client_error); + jack_on_shutdown(d->client, client_shutdown, 0); + + d->output_port_left = jack_port_register(d->client,"out-left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput,0); + d->output_port_right = jack_port_register(d->client,"out-right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput,0); + + if (jack_activate(d->client)) { + alert("Cannot activate client"); + return; + } + if (no_jack_conn) { + okay=true; + return; + } + int res=0; + const char *port1="playback_1", *port2="playback_2"; + //const char *port1="system:playback_1", *port2="system:playback_2"; + //const char *port1="alsa_pcm:playback_1", *port2="alsa_pcm:playback_2"; + const char **ports=jack_get_ports(d->client,0,JACK_DEFAULT_AUDIO_TYPE,0); //alsa*",0,0); + if (ports && ports[0]) { + for (int i=0;ports[i] && res<2;++i) { + if (strstr(ports[i],port1)) { ++res; port1=ports[i]; } + else if (strstr(ports[i],port2)) { ++res; port2=ports[i]; } + printf("port[%d]=%s\n",i,ports[i]); + } + } + else { + alert("No ports: ports=%p ports[0]=%d",ports,ports?ports[0]:0); + return; + } + if (res==2) { + if (jack_connect(d->client,jack_port_name(d->output_port_left),port1) || + jack_connect(d->client,jack_port_name(d->output_port_right),port2)) + alert("Cannot connect output ports"); + } + else { + alert("No alsa ports"); + alert("Expected: %s, %s",port1,port2); + // still okay! + } + free(ports); + okay=true; +} diff --git a/src/snd-interface.h b/src/snd-interface.h new file mode 100644 index 0000000..5f29f85 --- /dev/null +++ b/src/snd-interface.h @@ -0,0 +1,24 @@ +struct SndInterf { + int periodsize; // from alsa + bool okay; + SndInterf(); + ~SndInterf(); + void snd_write(short *buf); +}; + +struct JackInterf { + const char *client_name; + bool okay, + &is_playing; + bool (*play)(float *buf_left,float *buf_right); + int done, // e.g. 'done' + buf_size, // from jack + sample_rate; // from jack + struct Local *d; + JackInterf(const char *client_name,bool (*_play)(float *buf_left,float *buf_right),int _done,bool &is_playing); + ~JackInterf(); +}; + +extern int SAMPLE_RATE, + IBsize; +extern bool no_jack_conn; diff --git a/src/sound.cpp b/src/sound.cpp new file mode 100644 index 0000000..4344c37 --- /dev/null +++ b/src/sound.cpp @@ -0,0 +1,1816 @@ +#include +#include +#include +#include +#include + +#include "amuc-headers.h" +#include "snd-interface.h" + +static int tscale; + +const int + sbm=512, // 256 is about okay, but less: no clean low tones + amp_mul=20000, // multiplier for instr amplitude + phm_amp_mul1=20000, // multiplier for phys model ampl (20000 = distorsion limit) + at_scale=2, // attack scaling + ad_max=4, // attack and decay + // act_tempo=10 -> 100 beats/min = 100*4/60 note_units/sec -> tscale = 44100 * 10 / subdiv * 60 / 4 / 100 + voice_max=50, + mk_voice_max=5, // midi keyboard voices + stereo_delay=88; // delay = stereo_delay * 1/44KHz = 2 ms + +const float + phm_amp_mul2=2., + wave_amp_mul=1.5, // multiplier for sample from wave file + synth_amp_mul=0.3; // multiplier for synth amplitude +float + freq_scale, // 0.5*sbm/SAMPLE_RATE, + mid_c; // freq_scale * 261.65; // middle C frequency + +bool sample_rate_changed=false; // maybe true if output_port = eJack + +enum { + eSleep=1, ePause, eNote, ePortaNote, eSampled, ePhysMod, eSynthNote // note types +}; + +enum { + eStarting=1, eOcc, eDecaying // keyboard notes +}; + +SndInterf *snd_interface; +JackInterf *jack_interface; +KeybTune kb_tune; +Synth *synths[colors_max]; +void *start_sampler(void *arg); + +static pthread_mutex_t mtx= PTHREAD_MUTEX_INITIALIZER; + +static int minmax(int a,int x,int b,bool& clip) { + if (x>b) { clip=true; return b; } + if (xb) { clip=true; return b; } + if (x=keyb_notes_max-1) { + alert("more then %d notes",keyb_notes_max); + return; + } + ++cur_ind; + KeybNote *kn=buf+cur_ind; + kn->lnr=lnr; + kn->sign=sign; + kn->snr=snr1; + kn->dur=max(1,snr2-snr1); + kn->col=col; +} + +struct CurrentValues { // set by playScore(), used by MidiNoteBufs and play() + int snr, // current section nr + snr_lazy, // idem, updated when needed, used for sync with keyboard and display + tcount, + play_start; // set by fill_note_bufs() +} cur; + +struct Sinus { + int i_buf[sbm]; + float fl_buf[sbm]; + Sinus() { + int i,f; + for (i=0;i=dim ? i-dim : i; } + +void lowpass(float *in,float *out,const int dim,const int coff) { + for (int i=0;i(ind_f); + if (ind >= sbm) { ind-=sbm; ind_f-=sbm; } + val=sinus.fl_buf[ind]; + } +}; + +struct Reverb { + static const int taps=10, + phasing_ampl=40; + float *buf,*buf2; + int dim, + cur_pos, + revb_val; // 0 - 4 + const int *dpos; + bool is_ph; // phasing? + double lp,lp2; + float lfo_pos; + Instrument *the_instr; + Lfo lfosc; + Reverb(): + is_ph(false),lfo_pos(0.),the_instr(0) { + dim=set(4,0)+1+phasing_ampl; + buf=new float[dim]; + buf2=new float[dim]; + clear_bufs(); + } + void clear_bufs() { + cur_pos=0; lp=lp2=0.; + for (int i=0;i0; + if (is_ph) + lfosc.freq_mult= phasing==1 ? 1 : 2; + dpos= val==1 || val==3 ? fib+1 : fib+3; + return dpos[taps-1]; // needed to calculate buffer size + } + int i2ind(int i) { if (i>dim-1) i-=dim; else if (i<0) i+=dim-1; return i; } + void process(float& in) { + double val=0,val2=0; + if (is_ph) { + lfosc.update(lfo_pos); + int diff=int(lfosc.val*phasing_ampl-float(rand())/RAND_MAX); // dither + for (int i=0;irevb=0; + reverb.the_instr=inst; + inst->revb=&reverb; + reverb.set(val,phasing); +} + +bool is_sampled(int cat) { return cat==eSampled || cat==ePhysMod; } + +struct Note { + uchar cat, + note_col, + note_gr, + midi_nr, // MIDI freq nr; + midi_instr, + note_lnr,note_sign, + midi_ampl; + bool first_entry, + note_stop, + ctr_pitch; // controlled pitch? + CtrlBase *note_ctrl; + int dur, + dur1, // eNote: delta snr, ePause: pause * subdiv + attack, + decay, + remain, // remaining time, will be re-assigned + act_snr, // section nr + *ampl_ptr; + float lst_ind, // last index in wave buffer + freq; + void set_timing(int d,int t) { + dur1=d; + dur=remain=t; + note_stop=false; + } + void set(int as,uchar ncol,uchar ngr,CtrlBase *ctrl,float f,uchar lnr,uchar sign,bool sampled) { + note_lnr=lnr; note_sign=sign; + act_snr=as; + note_col=ncol; + note_gr=ngr; + note_ctrl=ctrl; + freq=f; + first_entry=true; + midi_instr=0; + if (sampled) { + cat=eSampled; + if (app->task==eMidiout) { + if (!(midi_instr=midi_out.col2smp_instr[note_gr][note_col])) + alert("warning: unmapped midi perc. instr., color %s, group %d", + color_name[note_col],note_gr); + } + ampl_ptr=ctrl->get_ampl_ptr(true,0); + ctr_pitch=ctrl->ctrl_pitch->value; + lst_ind=0.; + } + else { + cat=eNote; + if (app->task==eMidiout) { + int mi=midi_out.col2midi_instr[ngr][ncol]; + if (mi<0) { + alert("warning: unmapped midi instrument, color %s, group %d",color_name[ncol],ngr); + mi=0; + } + midi_instr=mi; + } + midi_nr=lnr_to_midinr(lnr,sign);// needed for midi output and mono-synth + ampl_ptr=ctrl->get_ampl_ptr(false,note_gr); + } + midi_ampl=midi_amplitude(); + } + int midi_amplitude() { + int mult=0; + switch (cat) { + case eNote: + case ePortaNote: + case eSynthNote: mult=500; break; + case eSampled: + case ePhysMod: mult=700; break; + default: alert("midi_amp: cat=%d",cat); + } + return minmax(1,int(ampl_mult[*ampl_ptr] * mult),127); + } +}; + +Note* NOTES[voice_max]; + +struct MidiNote { + int ev_time; + Note *note; + bool start; // note on? + MidiNote(Note *n,int ev,bool st):ev_time(ev),note(n),start(st) { } + bool operator<(MidiNote &other) { + return ev_time < other.ev_time || (ev_time==other.ev_time && !start); + } + bool operator==(MidiNote &other) { return false; } // always insert +}; + +SLinkedList midi_events[groupnr_max][colors_max], + midi_perc_events; + +struct NBufBase { + float ind_f,ind_f2,ind_f3; +}; + +struct NoteBuffer:NBufBase { + int busy, // note duration + decay + len, + cur_note; + Note *notes; + CtrlBase *ctrl; + NoteBuffer(): + len(20), + notes(new Note[len]) { + reset(); + } + void reset(); + void renew(); + void report(int); +}; + +struct MidiNoteBuf:NBufBase { + int lnr,sign,start_snr, + occ, // eOcc, eDecaying + mn_col; + float freq; + Instrument *m_instr; + MidiNoteBuf() { reset(); } + void reset(); +}; + +const float ampl_mult[ampl_max+1]={ 0,0.06,0.09,0.12,0.18,0.25,0.35,0.5,0.7,1.0 }; // index 0 - 9 + +struct iRed:Instrument { + float *at, + *b, + b1,b2, + at1; + int tone_val; + void set(int x,int y,int formant,int nr) { + var_sinus[x][y]=new VarSinus(formant,nr); + } + iRed():Instrument(3,2,eRight) { + set(0,0,7,2); set(0,1,7,3); set(0,2,7,5); + set(1,0,5,2); set(1,1,5,3); set(1,2,5,5); + set(2,0,3,2); set(2,1,3,3); set(2,2,3,5); + set(3,0,2,2); set(3,1,2,3); set(3,2,2,5); + set(4,0,1,2); set(4,1,1,3); set(4,2,1,5); + at=var_sinus[3][1]->buf; + b=var_sinus[2][1]->buf; + sbuf=new Sound(&b1,1.0); + float ad2[4]={ 1.0,1.0,1.0,1.0 }; + float dd[4]={ 1.0,0.6,0.4,0.2 }; + attack_data=new Sound[4]; + decay_data=new Sound[4]; + float *ad1[4]={ &at1,&at1,&b2,&b1 }; + for (int i=0;i<4;++i) { + attack_data[i]=Sound(ad1[i],ad2[i]); + decay_data[i]=Sound(&b1,dd[i]); + } + } + void nxt_val(float freq,NBufBase *nb) { + nb->ind_f += freq; + int ind=static_cast(nb->ind_f); + if (ind >= sbm) { ind-=sbm; nb->ind_f-=sbm; } + switch (tone_val) { + case 0: + at1=at[ind]; + b1=b[ind]; + break; + case 1: + nb->ind_f2 += freq*2.01; + int ind2=static_cast(nb->ind_f2); + if (ind2 >= sbm) { ind2-=sbm; nb->ind_f2-=sbm; } + at1=(at[ind] + at[ind2]) / 2; + b1=(b[ind] + b[ind2]) / 2; + break; + } + b2=(at1+b1)/2; + } + void set_start_timbre(int f,int n) { + at=var_sinus[f][n]->buf; + } + void set_timbre(int f,int n) { + b=var_sinus[f][n]->buf; + } + void set_start_ampl(int n) { // n from 0 to 3 + static const float sa[4][3] = { { 0,0.5,0.7 },{ 0.5,0.7,0.9 }, + { 1.0,1.0,1.0 },{ 1.5,1.5,1.3 } }; + for (int i=0;i<3;++i) attack_data[i].ampl=sa[n][i]; + } +} ired; + +struct FMinstr:Instrument { + float fm_freq, + fm_mod, + detun, + mm_val; + float b; + bool mm_enab; // fm-modulation modulation? + int mult_freq; + Lfo lfosc; + FMinstr(int loc):Instrument(0,1,loc), + fm_freq(3.0), + fm_mod(2.0/sbm), + detun(0), + mm_enab(false), + mult_freq(1) { + float ad[]={ 0.2,0.5,0.7,1.0 }; + float dd[]={ 1.0,0.7,0.4,0.2 }; + attack_data=new Sound[4]; + decay_data=new Sound[4]; + sbuf=new Sound(&b,1.0); + for (int i=0;i<4;++i) { + attack_data[i]=Sound(&b,ad[i]); + decay_data[i]=Sound(&b,dd[i]); + } + } + void nxt_val(float freq,NBufBase *nb) { + int fm,ind_fm,ind; + freq*=mult_freq; + nb->ind_f2 += (freq+detun)*fm_freq; + ind_fm=static_cast(nb->ind_f2); + if (ind_fm >= sbm) { ind_fm-=sbm; nb->ind_f2-=sbm; } + fm=sinus.i_buf[ind_fm]; + if (mm_enab) { + lfosc.update(nb->ind_f3); + nb->ind_f += freq*(1.0+fm*(fm_mod*(1.0+lfosc.val*mm_val))); + } + else + nb->ind_f += freq*(1.0+fm*fm_mod); + ind=static_cast(nb->ind_f); + if (ind>=sbm) { ind-=sbm; nb->ind_f-=sbm; } + else if (ind<0) { ind+=sbm; nb->ind_f+=sbm; } + b=sinus.fl_buf[ind]; + } + void set_detune(int n) { + static const float scale[6]={ 0,1,2.5,5,10,20 }; // detune value 0 - 5 + detun=scale[n]*freq_scale; + } +} iblack(eLeft),ibrown(eRight); + +void FMCtrl::setfm(int m) { + static const float + freq_arr[9] = { 0.5, 1, 2, 3, 4, 5, 6, 7, 8 }, + index_arr[8] = { 0, 0.5, 1, 2, 3, 5, 7, 10 }; + static const int mf_arr[9]={ 2,1,1,1,1,1,1,1,1 }; + float index; + FMinstr *instr=static_cast(inst); + switch (m) { + case eModFreq: + instr->fm_freq=freq_arr[fm_ctrl->value.x+1]; // value: -1 - 7 + instr->mult_freq=base_freq->value ? mf_arr[fm_ctrl->value.x+1] : 1; + set_text(fm_ctrl->text_x,"%.1f",instr->fm_freq); + break; + case eModIndex: + index=index_arr[fm_ctrl->value.y]; // value: 0 - 7 + instr->fm_mod=index/sbm; + set_text(fm_ctrl->text_y,"%.1f",index); + break; + } +} + +void FMCtrl::set_mmod() { + static const float + mmval_arr[6] = { 0, 0.2, 0.3, 0.4, 0.6, 0.8 }, + mmfreq_arr[4]= { 0.5, 1., 3., 10. }; + FMinstr *instr=static_cast(inst); + instr->mm_val=mmval_arr[mod_mod->value.x]; + instr->mm_enab= mod_mod->value.x!=0; + float mmfreq=mmfreq_arr[mod_mod->value.y]; + instr->lfosc.freq_mult=mmfreq; + set_text(mod_mod->text_x,"%.1f",instr->mm_val); + set_text(mod_mod->text_y,"%.1f",mmfreq); +} + +void FMCtrl::set_attack() { inst->set_attack(attack->value()); } + +void FMCtrl::set_decay() { inst->set_decay(decay->value()); } + +void FMCtrl::set_detune() { static_cast(inst)->set_detune(detune->value()); } + +void RedCtrl::set_startup() { ired.set_startup(startup->value()); } + +void RedCtrl::set_decay() { ired.set_decay(decay->value()); } + +void RedCtrl::set_start_amp() { ired.set_start_ampl(start_amp->value); } + +void RedCtrl::set_start_timbre() { + ired.set_start_timbre(start_timbre->value.x-1,start_timbre->value.y-2); // value: 1 - 4, 2 - 4 +} + +void RedCtrl::set_timbre() { + ired.set_timbre(timbre->value.x-1,timbre->value.y-2); // value: 1 - 5, 2 - 4 +} + +void RedCtrl::set_tone() { ired.tone_val=tone->act_rbutnr(); } + +struct iGreen:Instrument { + float b, + *wave1,*wave2; + float fmult; + iGreen():Instrument(1,3,eRight), + wave1(var_sinus[1][1]->buf), + wave2(var_sinus[2][0]->buf), + fmult(2.01) { + float ad[4]={ 0.2,0.5,0.7,1.0 }; + float dd[4]={ 1.0,0.7,0.4,0.2 }; + sbuf=new Sound(&b,1.0); + attack_data=new Sound[4]; + decay_data=new Sound[4]; + for (int i=0;i<4;++i) { + attack_data[i]=Sound(&b,ad[i]); + decay_data[i]=Sound(&b,dd[i]); + } + } + void set_timbre1(int f,int n) { + wave1=var_sinus[f][n]->buf; + } + void set_timbre2(int f,int n) { + wave2=var_sinus[f][n]->buf; + } + void nxt_val(float freq,NBufBase *nb) { + nb->ind_f += freq; + int ind=static_cast(nb->ind_f); + if (ind >= sbm) { ind-=sbm; nb->ind_f-=sbm; } + nb->ind_f2 -= fmult*freq; + int ind2=static_cast(nb->ind_f2); + if (ind2<0) { ind2+=sbm; nb->ind_f2+=sbm; } + b=(wave1[ind] + wave2[ind2])/2; + } +} igreen; + +void GreenCtrl::set_attack() { igreen.set_attack(attack->value()); } + +void GreenCtrl::set_decay() { igreen.set_decay(decay->value()); } + +void GreenCtrl::set_timbre1() { + igreen.set_timbre1(timbre1->value.x-1,timbre1->value.y-2); // value: 1 - 3, 2 - 4 +} + +void GreenCtrl::set_timbre2() { + igreen.set_timbre2(timbre2->value.x-1,timbre2->value.y-2); // value: 1 - 3, 2 - 4 +} + +void GreenCtrl::set_freq_mult() { + static const float fmul[4][4]={ { 1.001,1.501,2.001,3.001 }, // freq mult + { 1.002,1.502,2.002,3.002 }, + { 1.003,1.503,2.003,3.003 }, + { 1.006,1.506,2.006,3.006 } }; + int ind1=freq_mult->value(), + ind2=chorus->value(); + igreen.fmult=fmul[ind2][ind1]; + set_text(freq_mult->text,"%.1f",igreen.fmult); +} + +struct Pulse { + float puls[sbm], + buf[sbm], + buf3[sbm]; + int coff; // lowpass cutoff freq + Pulse():coff(20) { + const float slope=0.032, + bias=-0.25; + int n, + i1=int(1/slope), + i2=4*i1, + i3=12*i1; + puls[0]=bias; + for (n=1;nblue_control->chorus->value, + &rich=app->blue_control->rich->value; + nb->ind_f += freq; + ind=static_cast(nb->ind_f); + if (ind >= sbm) { ind-=sbm; nb->ind_f-=sbm; } + if (chorus) { + nb->ind_f2 -= freq + diff; + int ind2=static_cast(nb->ind_f2); + if (ind2 < 0) { ind2 += sbm; nb->ind_f2 += sbm; } + if (rich) b=pulse.buf3[ind] - pulse.buf3[ind2]; + else b=pulse.buf[ind] - pulse.buf[ind2]; + } + else if (rich) + b=pulse.buf3[ind]; + else + b=pulse.buf[ind]; + } +} iblue; + +void BlueCtrl::set_attack() { iblue.set_attack(attack->value()); } + +void BlueCtrl::set_decay() { iblue.set_decay(decay->value()); } + +void BlueCtrl::set_durlim() { iblue.set_durl(dur_limit->value(),dur_limit->text); } + +void BlueCtrl::set_lowpass() { + static int coff[5]={ 2,10,20,40,80 }; + pulse.coff=coff[lowpass->value()]; + pulse.fill_buf(); +} + +void RedCtrl::set_durlim() { ired.set_durl(dur_limit->value(),dur_limit->text); } + +struct iPurple:Instrument { + float *at, + *b, + at1,at2,b1; + int snd_val; + float st_ampl[harm_max], // startup amplitudes + ampl[harm_max]; // steady state + const int *frm; + Lfo lfosc; + iPurple():Instrument(2,2,eLeft) { + static const int freq_mult[harm_max]={ 1,2,3,6,9 }; + frm=freq_mult; + st_ampl[0]=st_ampl[1]=st_ampl[2]=st_ampl[3]=st_ampl[4]=2; + ampl[0]=ampl[1]=ampl[2]=ampl[3]=ampl[4]=1; + float *bd[4]={ &at1,&at1,&at2,&b1 }, + dd[4]={ 0.4,0.3,0.2,0.1 }; + sbuf=new Sound(&b1,0.4); + attack_data=new Sound[4]; + decay_data=new Sound[4]; + for (int i=0;i<4;++i) { + attack_data[i]=Sound(bd[i],0.4); + decay_data[i]=Sound(&b1,dd[i]); + } + lfosc.freq_mult=1; + } + void set_a(int *harmon,float *amp) { + static const float scale_ampl[4]={ 0,0.25,0.5,1.0 }; + for (int i=0;iind_f += freq; + ind=int(nb->ind_f); + while (ind >= sbm) { ind-=sbm; nb->ind_f-=sbm; } + switch (snd_val) { + case 0: + at1=b1=0; + for (int i=0;iind_f2 += freq * 1.001 + diff; + ind2=int(nb->ind_f2); + if (ind2 >= sbm) { ind2-=sbm; nb->ind_f2-=sbm; } + at1=b1=0; + for (int i=0;iind_f3); + aa=1.+lfosc.val/2.; // 0.8 -> 1.2 + bb=(1.-aa)/sbm; + ind=int(aa*ind + bb*ind*ind); + } + at1=b1=0; + for (int i=0;ivalue()); } + +void PurpleCtrl::set_tone() { ipurple.snd_val=sound->act_rbutnr(); } + +void PurpleCtrl::set_decay() { ipurple.set_decay(decay->value()); } + +void CtrlBase::set_loc(int loc) { inst->s_loc=loc; } + +void PhmCtrl::set_sampled_loc(int loc) { sampled_instr[ctrl_col].samp_loc=loc; } + +Instrument *col2instr(int note_col) { + switch (note_col) { + case eBlack: return &iblack; + case eRed: return &ired; + case eGreen: return &igreen; + case eBlue: return &iblue; + case eBrown: return &ibrown; + case ePurple: return &ipurple; + default: alert("col2instr: instr %d?",note_col); return &iblack; + } +} + +void MidiNoteBuf::reset() { + occ=0; + m_instr=0; + ind_f=ind_f2=ind_f3=0.; +} + +void NoteBuffer::reset() { + busy=0; + cur_note=-1; + ctrl=0; + ind_f=0; + ind_f2=ind_f3=rand()%sbm; +} + +void NoteBuffer::renew() { + int old_len=len; + len*=2; + Note *new_notes=new Note[len]; + for (int i=0;icat==eSleep) + return; + printf("Voice %d\n",n); + static const char *cat_name[]={ "","eSleep","ePause","eNote","ePortaNote","eSampled","ePhysMod","eSynthNote" }; + for (Note *np=notes;np-notes<=cur_note;np++) { + printf(" cat=%s ",cat_name[np->cat]); + if (np->cat!=eSleep) { + printf("act_snr=%d dur=%d dur1=%d ",np->act_snr,np->dur,np->dur1); + switch (np->cat) { + case ePause: + if (np->note_stop) printf("stop"); + break; + case eNote: + case ePortaNote: + printf("freq=%0.2f col=%u group=%u attack=%d decay=%d ", + np->freq,np->note_col,np->note_gr,np->attack,np->decay); + break; + case eSampled: + printf("col=%u group=%u dur/tempo=%d",np->note_col,np->note_gr,np->dur/app->act_tempo); + break; + case ePhysMod: + printf("col=%u group=%u dur/tempo=%d",np->note_col,np->note_gr,np->dur/app->act_tempo); + break; + case eSynthNote: + printf("synth=%p freq=%0.2f col=%u group=%u ", + np->note_ctrl->synth,np->freq,np->note_col,np->note_gr); + break; + } + } + putchar('\n'); + } +} + +float line_freq(int lnr,int sign) { // ScLine -> frequency + static const float + a=440, bes=466.2, b=493.9, c=523.3, cis=554.4, d=587.3, + dis=622.3, e=659.3, f=698.5, fis=740.0, g=784.0, gis=830.6, + + F[sclin_max]= { + b*8,a*8,g*4,f*4,e*4,d*4,c*4, + b*4,a*4,g*2,f*2,e*2,d*2,c*2, + b*2,a*2,g, f, e, d, c, + b, a, g/2,f/2,e/2,d/2,c/2, // F[27] = middle C + b/2,a/2,g/4,f/4,e/4,d/4,c/4, + b/4,a/4,g/8,f/8,e/8,d/8,c/8, + b/8,a/8,g/16 + }, + Fhi[sclin_max]= { + c*8,bes*8,gis*4,fis*4,f*4,dis*4,cis*4, + c*4,bes*4,gis*2,fis*2,f*2,dis*2,cis*2, + c*2,bes*2,gis, fis, f, dis, cis, + c, bes, gis/2,fis/2,f/2,dis/2,cis/2, + c/2,bes/2,gis/4,fis/4,f/4,dis/4,cis/4, + c/4,bes/4,gis/8,fis/8,f/8,dis/8,cis/8, + c/8,bes/4,gis/16 + }, + Flo[sclin_max]= { + bes*8,gis*4, fis*4,e*4,dis*4,cis*4,b*4, + bes*4,gis*2, fis*2,e*2,dis*2,cis*2,b*2, + bes*2,gis, fis, e, dis, cis, b, + bes, gis/2, fis/2,e/2,dis/2,cis/2,b/2, + bes/2,gis/4, fis/4,e/4,dis/4,cis/4,b/4, + bes/4,gis/8, fis/8,e/8,dis/8,cis/8,b/8, + bes/8,gis/16,fis/16 + }; + switch (sign) { + case 0: return F[lnr]*freq_scale; + case eHi: return Fhi[lnr]*freq_scale; + case eLo: return Flo[lnr]*freq_scale; + default: alert("sign=%u?",sign); return F[lnr]*freq_scale; + } +} + +int same_color(ScSection *fst,ScSection *sec,ScSection *end,ScSection*& lst) { + int n; + uint col=sec->s_col, group=sec->s_group, + sign=sec->sign, + sampled=sec->sampled; + lst=0; + for (n=1;;++n,sec=fst) { + for (;;sec=sec->nxt()) { + if (sec->s_col==col && sec->s_group==group && sec->sign==sign && sec->sampled==sampled && sec->cat!=ePlay_x) { + sec->cat=ePlay_x; lst=sec; + break; + } + if (!sec->nxt_note) return n-1; + } + if (sec->stacc || sec->sampled) return n; + if (++fst>=end) return n; + if (fst->cat==eSilent) return n; + for (sec=fst;;sec=sec->nxt()) { + if (sec->cat==ePlay) break; + if (!sec->nxt_note) return n; + } + } +} + +struct NoteBuffers { + bool init; + NoteBuffer nbuf[voice_max]; + int voice_end; + void reset() { + init=false; + voice_end=-1; + for (int i=0;i=0) + alert("voices > %d at measure %d",voice_max,cur_meas); + else + alert("voices > %d",voice_max); + v=nop; + } + bool fill_note_bufs(int play_start,int play_stop,Score *); +} nbufs; + +struct MidiNoteBufs { + int mk_voice, + key_arr[128]; // mapping midi nr -> nbuf index + MidiNoteBuf nbuf[mk_voice_max]; + void reset() { + int i; + mk_voice=nop; + for (i=0;i<128;++i) key_arr[i]=nop; + for (i=0;iocc=eStarting; + nb->start_snr=snr; + nb->lnr=lnr; + nb->freq=line_freq(lnr,sign); + nb->sign=sign; + nb->mn_col=col; + nb->m_instr=col2instr(col); + key_arr[midi_nr]=ind; + pthread_mutex_unlock(&mtx); + } + void keyb_note_off(int snr,int midi_nr) { + pthread_mutex_lock(&mtx); + if (key_arr[midi_nr]>nop) { + MidiNoteBuf *nb=nbuf+key_arr[midi_nr]; + nb->occ=eDecaying; + kb_tune.add(nb->lnr,nb->sign,nb->start_snr,snr,nb->mn_col); + } + key_arr[midi_nr]=nop; + pthread_mutex_unlock(&mtx); + } +} mk_nbufs; + +void keyb_noteOn(int midi_nr) { + mk_nbufs.keyb_note_on(cur.snr_lazy+kb_tune.turn_start,midi_nr,app->act_color); +} + +void keyb_noteOff(int midi_nr) { + mk_nbufs.keyb_note_off(cur.snr_lazy+kb_tune.turn_start,midi_nr); +} + +void MidiOut::make_midi() { // supposed: task = eMidiout + Note *note; + int voice, + col, + gr, + nr_tracks=0, + nr; + for (voice=0;voice<=nbufs.voice_end;++voice) { + for (note=NOTES[voice];note;++note) { + switch (note->cat) { + case ePause: + break; + case eSleep: + goto next_voice; + case eSampled: + case ePhysMod: + midi_perc_events.insert(MidiNote(note, note->act_snr, true), true); + break; + case eNote: + case ePortaNote: + case eSynthNote: + midi_events[note->note_gr][note->note_col].insert(MidiNote(note, note->act_snr, true), true); + midi_events[note->note_gr][note->note_col].insert(MidiNote(note, note->act_snr+note->dur1, false), true); + break; + } + } + next_voice:; + } + for (gr=0;gr *mn; + for (nr=0,gr=0;grnxt) { + note=mn->d.note; + note_onoff(mn->d.start, col, gr, note->midi_instr, + false, mn->d.ev_time, note->midi_nr, note->midi_ampl); + } + close_track(); + midi_events[gr][col].reset(); + } + } + mn=midi_perc_events.lis; + if (mn) { + init_perc_track(++nr); + for (;mn;mn=mn->nxt) { + note=mn->d.note; + note_onoff(mn->d.start, note->note_col, note->note_gr, note->midi_instr, + true, mn->d.ev_time, note->midi_nr, note->midi_ampl); + } + close_track(); + midi_perc_events.reset(); + } + close(); + app->dia_wd->dlabel("midi file created",cForeground); +} + +void PostscriptOut::create_postscript(Score *score) { // supposed: task = ePsout or eAbcout + Note *note; + int voice; + set(app->act_meter,app->nupq,app->mv_key_nr,app->title()); + + for (voice=0;voice<=nbufs.voice_end;++voice) { + for (note=NOTES[voice];note;++note) { + switch (note->cat) { + case ePause: + break; + case eSleep: + goto next_voice; + case eSampled: + case ePhysMod: + insert_perc(note->note_col,note->note_gr,note->act_snr); + break; + case eNote: + case ePortaNote: + case eSynthNote: + insert(note->note_col,note->note_gr,note->act_snr,note->note_lnr,note->note_sign,note->dur1); + break; + } + } + next_voice: + continue; + } + write_ps(app->task==eAbcout); + reset_ps(); + if (app->task==ePsout) + app->dia_wd->dlabel("postscript file created",cForeground); + else if (app->task==eAbcout) + app->dia_wd->dlabel("abc file created",cForeground); +} + +bool NoteBuffers::fill_note_bufs(int play_start,int play_stop,Score *score) { + int samecol=0, + v, // voice + lnr,snr, + pause, + decay; + bool no_durlim= app->task==eMidiout || app->task==ePsout || app->task==eAbcout, + no_readwav=no_durlim; + ScSection *sec,*lst_sect; + Note *note; + NoteBuffer *lst_nb=0; + reset(); + cur.play_start=play_start; + + for (snr=0;snrlen;++snr) // initialisation + if (!exec_info(snr,score,false)) return false; + + for (snr=play_start;snrlen;++snr) { + if ((play_stop>0 && snr>play_stop) || (score->end_sect && snr==score->end_sect)) { + kb_tune.tune_wid=snr-play_start; + break; + } + if (!exec_info(snr,score,false)) // there might be timing, decay modifications + return false; + for (lnr=0;lnrlin[lnr]; + sec=score->get_section(lnr,snr); + if (sec->cat==ePlay || sec->cat==ePlay_x) { + for (;sec;sec=sec->nxt()) { + if (sec->cat==ePlay && + (app->play_1_col<0 || (sec->s_col==(uint)app->play_1_col && sec->sampled==app->sampl->value))) { + find_free_voice(sec->s_col,sec->s_group,sec->del_start,v,pause,snr/app->act_meter); // uses: busy + if (v==nop) break; + NoteBuffer *const nb=nbuf+v; + lst_nb=nb; + samecol=same_color(sc_line->sect+snr,sec,sc_line->sect+score->len,lst_sect); + nb->ctrl=sec->sampled ? col2phm_ctrl(sec->s_col) : col2ctrl(sec->s_col); + const Instrument *inst=nb->ctrl->isa[sec->s_group]->inst; + if (inst->dur_lim>0 && !no_durlim) { + samecol=min(samecol,inst->dur_lim); + } + nb->busy=samecol*subdiv + lst_sect->del_end; + if (pause > 0) { + if (++nb->cur_note >= nb->len) nb->renew(); + note=nb->notes + nb->cur_note; + note->cat=ePause; + note->set_timing(pause,tscale*pause); + } + if (++nb->cur_note>=nb->len) nb->renew(); + note=nb->notes+nb->cur_note; + note->set( + snr, + sec->s_col,sec->s_group, + nb->ctrl, + line_freq(lnr,sec->sign), + lnr,sec->sign, + sec->sampled + ); + if (note->cat==eSampled) { + if (no_readwav) { + note->set_timing(1,0); + nb->busy=0; + } + else { + int dur; + if (nb->ctrl->sample_mode==eWaveMode) { + int size=wave_buf.w_buf[sec->s_col].size; + if (!size) { + alert("%s: no wave data",color_name[sec->s_col]); + return false; + } + if (size<0 || size>100*SAMPLE_RATE) { + alert("wave data corrupted? (size=%d)",size); + return false; + } + dur=size * app->act_tempo; + } + else if (nb->ctrl->sample_mode==ePhysmodMode) { + note->cat=ePhysMod; + dur=phm_buf.var_data[sec->s_col]->size * app->act_tempo; + } + else { + alert("fill_note_bufs: unexpected sample_mode %d",nb->ctrl->sample_mode); + dur=0; + } + if (note->ctr_pitch) dur=pad(int(dur * mid_c / note->freq),tscale); + else if (sample_rate_changed) dur=pad(int(float(dur) * SAMPLE_RATE / 44100),tscale); + else dur=pad(dur,tscale); + nb->busy=dur / tscale + sec->del_start; + note->set_timing(1,dur); + note->attack=note->decay=0; + } + } + else { // note->cat = eNote + if (note->note_ctrl->instrm_val==eSynthMode) { + note->cat=eSynthNote; + int dur=samecol * subdiv - sec->del_start + lst_sect->del_end - 1; // -1: staccato notes separated + nb->busy=dur + sec->del_start; + note->set_timing(samecol,dur*tscale); + note->attack=note->decay=0; + } + else { + if ((!inst->dur_lim || no_durlim) && lst_sect->port_dlnr) + note->cat=ePortaNote; + note->set_timing(samecol,tscale * (samecol * subdiv - sec->del_start + lst_sect->del_end)); + if (inst->attack) + note->attack=min(tscale * inst->attack / at_scale,note->dur); + else note->attack=0; + if (note->cat==ePortaNote) { + int dlnr, + dsnr, + new_snr=snr, + new_lnr=lnr; + ScLine *new_sc_line; + Note *new_note; + for (;;) { + dlnr=lst_sect->port_dlnr; + dsnr=lst_sect->port_dsnr; + new_snr+=samecol+dsnr; + new_lnr+=dlnr; + new_sc_line=&score->lin[new_lnr]; + for (sec=new_sc_line->sect+new_snr;sec;sec=sec->nxt()) + if (sec->cat==ePlay && sec->s_col==note->note_col && sec->s_group==note->note_gr) break; + if (sec && sec->cat==ePlay) { + note->decay=tscale * (dsnr * subdiv - lst_sect->del_end + sec->del_start); + samecol=same_color(new_sc_line->sect+new_snr,sec,new_sc_line->sect+score->len,lst_sect); + if (++nb->cur_note>=nb->len) nb->renew(); + new_note=nb->notes+nb->cur_note; + new_note->set( + new_snr,note->note_col,note->note_gr,note->note_ctrl, + line_freq(new_lnr,sec->sign), + new_lnr,sec->sign, + false // cannot be sampled + ); + if (inst->dur_lim>0 && !no_durlim) { + samecol=min(samecol,inst->dur_lim); + } + else if (lst_sect->port_dlnr) + new_note->cat=ePortaNote; + new_note->set_timing(samecol, + tscale * (samecol*subdiv - sec->del_start + lst_sect->del_end)); + new_note->attack=0; + if (new_note->cat==eNote) { + if (inst->decay) { + if (lst_sect->stacc && inst->dur_lim==0) { decay=1; new_note->decay=tscale; } + else { + decay=inst->decay; + new_note->decay=tscale * decay; + } + } + else + new_note->decay=decay=0; + nb->busy=(new_snr-snr+samecol)*subdiv+decay; + break; + } + } + else { + alert("unterminated portando"); + note->decay=tscale; + nb->busy=(new_snr-snr+samecol)*subdiv+1; + break; + } + note=new_note; + } + if (!sec) break; // unterminated portando? + } + else if (inst->decay) { + if (lst_sect->stacc && inst->dur_lim==0) { + nb->busy+=1; note->decay=tscale; + } + else { + decay=inst->decay; nb->busy+=decay; + note->decay=tscale * decay; + } + } + else note->decay=0; + } + } + } + } + } + } + for (v=0;v0 || score->end_sect>0) { + find_free_voice(0,0,0,v,pause,nop); // add pause note at end of last NoteBuffer + if (v>nop) { + nb1=nbuf+v; + if (++nb1->cur_note>=nb1->len) nb1->renew(); + note=nb1->notes+nb1->cur_note; + note->cat=ePause; + note->set_timing(pause,pause*tscale); + if (app->repeat) note->note_stop=true; + } + } + for (v=0;v<=voice_end;++v) { // all note bufs end with cat=eSleep + nb1=nbuf+v; + if (++nb1->cur_note>=nb1->len) nb1->renew(); + nb1->notes[nb1->cur_note].cat=eSleep; + } + if (debug) { + puts("-----------"); + for (v=0;v<=nbufs.voice_end;++v) nbuf[v].report(v); + } + for (v=0;v<=nbufs.voice_end;++v) NOTES[v]=nbufs.nbuf[v].notes; // nbuf[v] may have been realloced + exec_info(0,score,true); // maybe initialize timing + return true; +} + +void restore_marked_sects(Score* sc,int play_start) { // restore ePlay_x notes + ScSection *sec; + for (int lnr=0;lnrget_section(lnr,0); + for (int snr=play_start;snrlen;++snr) { + for (sec=sect+snr;sec;sec=sec->nxt()) { + if (sec->cat==ePlay_x) sec->cat=ePlay; + } + } + } +} + +bool init_phmbuf() { return phm_buf.init(phm_amp_mul1); } + +void App::playScore(int play_start,int play_stop) { + cur.snr=cur.snr_lazy=cur.tcount=0; + + if (!repeat) { + for (int n=0;nclear_buffers(); + col2ctrl(n)->synth->init(false); + synths[n]=0; + } + reverb.clear_bufs(); + } + else { + for (int n=0;nsynth->init(true); + } + } + bool ok=nbufs.fill_note_bufs(play_start,play_stop,cur_score); + restore_marked_sects(cur_score,play_start); + if (ok) { + if (task==eMidiout) { + midi_out.make_midi(); + task=0; + } + else if (task==ePsout || task==eAbcout) { + ps_out.create_postscript(cur_score); + task=0; + } + else if (task==eDumpwav) { + pthread_create(&thread1, 0, start_sampler, 0); + } + else if (output_port==eAlsa) + pthread_create(&thread1, 0, start_sampler, 0); + else if (output_port==eJack) { + send_uev('repm',cur.snr); + if (jack_interface) + i_am_playing=true; + else { alert("jack not running?"); i_am_playing=false; } + } + else alert("task?"); + } + else { + if (task==eMidiout) { + midi_out.close(); + for (int gr=0;grcur_score; + int n,col, + ind_instr_buf; // index for instrument buffers + float tmp_buf_r[IBsize], + tmp_buf_l[IBsize], + tmp_buf_sc[IBsize], + value, + mix; + + Sound *snd1,*snd2; + Note *note; + NoteBuffer *nbuf; + MidiNoteBuf *mk_nbuf; + Instrument *instr; + iSampled *samp_ins; + Synth *the_synth; + int tempo=app->act_tempo; + int stop_req=1; + ShortBuffer *phm=0, + *wav=0; + n=cur.tcount * tempo * IBsize / tscale / subdiv; + if (cur.tcount==0 || n>cur.snr_lazy) // needed for keyboard tune + cur.snr_lazy=n; + ++cur.tcount; + for (col=0;colbuf's + instr=col2instr(col); + samp_ins=sampled_instr+col; + for (n=0;nbuf[n]=0.; + samp_ins->sample_buf[n]=0.; + } + } + if (mk_connected) + for (int mk_voice=0;mk_voiceocc) { + instr=mk_nbuf->m_instr; + for (n=0;nnxt_val(mk_nbuf->freq,mk_nbuf); + snd1=instr->sbuf; + switch (mk_nbuf->occ) { + case eStarting: + value=snd1->wave[0] * n / IBsize; + break; + case eDecaying: + value=snd1->wave[0] * (IBsize-n) / IBsize; + break; + default: + value=snd1->wave[0]; + } + int ampval=col2ctrl(mk_nbuf->mn_col)->ampl_val[0].value; + instr->buf[n]+=value * ampl_mult[ampval]; + } + switch (mk_nbuf->occ) { + case eStarting: mk_nbuf->occ=eOcc; break; + case eDecaying: mk_nbuf->occ=0; break; + } + } + pthread_mutex_unlock(&mtx); + } + for (int voice=0;voice<=nbufs.voice_end;++voice) { + nbuf=nbufs.nbuf+voice; + ind_instr_buf=-1; + note=NOTES[voice]; + loop_start: + ++ind_instr_buf; + switch (note->cat) { + case eSleep: + break; + case ePause: + if (stop_req!=2) stop_req=0; + for (;ind_instr_bufremain; + if (n > 0) note->remain-=tempo; + else if (note->note_stop) + stop_req=2; + else { + note = ++NOTES[voice]; + note->remain += n; + goto loop_start; + } + } + break; + case eSampled: + case ePhysMod: { + if (stop_req!=2) stop_req=0; + if (cur.snr < note->act_snr) + for (++cur.snr;;++cur.snr) { + if (!exec_info(cur.snr,score,true)) { app->stop_requested=true; break; } + if (cur.snr % app->act_meter == 0 && cur.snr>=cur.play_start) // update measure nr display + send_uev('repm',cur.snr); + if (cur.snr == note->act_snr) break; + } + if (note->cat==eSampled) + wav=wave_buf.w_buf+note->note_col; + else if (note->cat==ePhysMod) + phm=phm_buf.var_data[note->note_col]; + else { alert("play sampled: cat=%d",note->cat); return false; } + float cnt_f, + *sb=sampled_instr[note->note_col].sample_buf; + const float inc_f=note->ctr_pitch ? note->freq/mid_c : sample_rate_changed ? 44100./SAMPLE_RATE : 1., + amp=ampl_mult[*note->ampl_ptr] * (note->cat==eSampled ? wave_amp_mul : phm_amp_mul2) / amp_mul; + for (cnt_f=note->lst_ind; + ind_instr_bufcat==eSampled) { + if (ind>=0 && indsize) + sb[ind_instr_buf] += wav->buf[ind] * amp; + } + else { + if (ind>=0 && indsize) + sb[ind_instr_buf] += phm->buf[ind] * amp; + } + n=note->remain; + if (n > 0) note->remain-=tempo; + else { + note = ++NOTES[voice]; + note->remain += n; + goto loop_start; + } + } + note->lst_ind=cnt_f; + } + break; + case eNote: + case ePortaNote: + case eSynthNote: + if (stop_req!=2) stop_req=0; + if (cur.snr < note->act_snr) { + for (++cur.snr;;++cur.snr) { + if (!exec_info(cur.snr,score,true)) { app->stop_requested=true; break; } + if (cur.snr % app->act_meter == 0 && cur.snr>=cur.play_start) // update measure nr display + send_uev('repm',cur.snr); + if (cur.snr == note->act_snr) break; + } + } + the_synth= note->cat==eSynthNote ? note->note_ctrl->synth : 0; + if (note->first_entry) { + note->first_entry=false; + if (the_synth) { + synths[note->note_col]=the_synth; + the_synth->set_values(note->midi_nr,note->freq/freq_scale/2.); + } + } + instr=note->note_ctrl->isa[note->note_gr]->inst; + + for (;ind_instr_bufremain; + if (n > 0) note->remain-=tempo; + else { + the_synth->note_off(); + note = ++NOTES[voice]; + note->remain += n; + goto loop_start; + } + continue; + } + if (note->remain>=0) { + instr->nxt_val(note->freq,nbuf); + if (note->attack) { + div_t d=div(max(0,ad_max * (note->dur - note->remain)),note->attack); + n=d.quot; + if (n >= ad_max-1) { + snd1=instr->attack_data + ad_max-1; + value=snd1->wave[0] * snd1->ampl; + } + else { + snd1=instr->attack_data + n; + snd2=snd1+1; + mix=float(d.rem) / note->attack; + value=snd1->wave[0] * snd1->ampl * (1.-mix) + snd2->wave[0] * snd2->ampl * mix; + } + } + else { + snd1=instr->sbuf; + value=snd1->wave[0] * snd1->ampl; + } + } + else if (note->cat==ePortaNote) { + Note *new_note=note+1; + if (note->decay) { + mix=float(-note->remain)/note->decay; + instr->nxt_val(note->freq*(1.0-mix) + new_note->freq*mix,nbuf); + } + else instr->nxt_val(note->freq,nbuf); + snd1=instr->sbuf; + value=snd1->wave[0] * snd1->ampl; + } + else if (note->decay) { + instr->nxt_val(note->freq,nbuf); + div_t d=div(max(0,-ad_max*note->remain),note->decay); + n=d.quot; + snd1=instr->decay_data + n; + mix=float(d.rem)/note->decay; + if (nwave[0] * snd1->ampl * (1.-mix) + snd2->wave[0] * snd2->ampl * mix; + } + else if (n==ad_max-1) + value=snd1->wave[0] * snd1->ampl * (1.-mix); + else value=0; + } + else + value=0; + note->note_ctrl->inst->buf[ind_instr_buf]+=value*ampl_mult[*note->ampl_ptr]; + // own buffer is used, not the isa buffer, so stereo location will be kept + n=note->remain + note->decay; + if (n > 0) note->remain-=tempo; + else { + if (note->cat!=ePortaNote) { + nbuf->ind_f=0.; // ind_f2 not reset + } + note = ++NOTES[voice]; + note->remain += n; + goto loop_start; + } + } + break; + default: + alert("unknown note %d",note->cat); + } + } + float val, + val_r,val_l, + val_sc; // for scope + for (n=0;nbuf's + instr=col2instr(col); + samp_ins=sampled_instr+col; + if (synths[col]) { // any synthesizer active? + if (!synths[col]->fill_buffer(instr->buf,IBsize,synth_amp_mul)) synths[col]=0; + } + if (instr->revb && instr->revb->revb_val) { + for (n=0;nrevb->process(instr->buf[n]); + } + for (n=0;nsample_buf[n]; + switch (samp_ins->samp_loc) { + case eRRight: + val_r=val; val_l=0; + break; + case eRight: + val_r=val; val_l=val*0.7; + break; + case eLeft: + val_l=val; val_r=val*0.7; + break; + case eLLeft: + val_l=val; val_r=0; + break; + default: + val_l=val_r=val; + } + val=instr->buf[n]; + val_sc+=val; + switch (instr->s_loc) { + case eRRight: + val_r+=val; + break; + case eRight: + val_r+=val; val_l+=instr->delay(val_r)*0.7; + break; + case eLeft: + val_l+=val; val_r+=instr->delay(val_l)*0.7; + break; + case eLLeft: + val_l+=val; + break; + default: + val_l+=val*0.8; val_r+=val*0.8; //instr->delay(val*0.8); + break; + } + tmp_buf_r[n]+=val_r; + tmp_buf_l[n]+=val_l; + tmp_buf_sc[n]+=val_sc; // scope + } + } + bool clipped=false; + if (output_port==eAlsa || app->task==eDumpwav) { + short buffer[IBsize*2]; + for (n=0;ntask==eDumpwav) { + if (!dump_wav((char*)buffer,IBsize*4)) { + alert("dump wave problem"); + app->task=0; + } + } + else + snd_interface->snd_write(buffer); + } + else { // output_port = eJack + for (n=0;nscopeView->insert(int(minmax(-20000,int(amp_mul*tmp_buf_sc[n]),20000)/700)); + } + send_uev('scop'); + if (app->stop_requested) + return false; + if (stop_req) + return false; + return true; +} + +void *start_sampler(void *arg) { // used if output_port = eAlsa, or if task = eDumpwav + if (app->task==0) { + snd_interface=new SndInterf(); + if (!snd_interface->okay) { + snd_interface=0; + i_am_playing=false; + return 0; + } + send_uev('repm',cur.snr); + i_am_playing=true; + while (play(0,0)); + delete snd_interface; + snd_interface=0; + } + else { // task = eDumpwav + while (play(0,0)); + } + send_uev('done'); + return 0; +} + +bool play_wfile(float *buf_left,float *buf_right) { + if (output_port!=eJack) { alert("play_wfile: output_port?"); wf_playing=false; return false; } + if (!wf_playing) return false; + bool stop=false; + int i,ind=0; + float ind_f; + const float inc_f=sample_rate_changed ? 44100./SAMPLE_RATE : 1.; + for (i=0,ind_f=0;iscopeView->insert(int(amp_mul*val/700)); + } + else { + stop=true; + break; + } + } + wl_buf.bpos+=ind; + send_uev('scop'); + if (!stop && !app->stop_requested) return true; + wf_playing=false; + return false; +} + +void *wave_listen(void *arg) { // used if output_port = eAlsa + if (output_port!=eAlsa) { alert("wave_listen: output_port?"); i_am_playing=false; return 0; } + SndInterf *snd_interf=new SndInterf(); + if (!snd_interf->okay) { + snd_interface=0; + i_am_playing=false; + return 0; + } + short buffer[IBsize*2]; + int i, + ind; + bool stop=false; + i_am_playing=true; + for (ind=0;!stop && !app->stop_requested;ind+=IBsize) { + for (i=0;iscopeView->insert(val/700); + } + send_uev('scop'); + snd_interf->snd_write(buffer); + } + delete snd_interf; + i_am_playing=false; + return 0; +} + +void set_time_scale() { + sample_rate_changed= SAMPLE_RATE!=44100; + tscale=SAMPLE_RATE/2; + freq_scale=0.5 * sbm / SAMPLE_RATE; + mid_c=261.65 * freq_scale; // middle C frequency, 1 octave lower then standard +} + +void set_buffer_size() { + iblack.buf=new float[IBsize]; + ired.buf=new float[IBsize]; + iblue.buf=new float[IBsize]; + igreen.buf=new float[IBsize]; + ipurple.buf=new float[IBsize]; + ibrown.buf=new float[IBsize]; + for (int n=0;napp_local->file_sel + PhmCtrl(Rect,int col); + HVSlider *speed_tension; + HSlider *wave_ampl, + *decay; + CheckBox *add_noise, + *just_listen; + void dump_settings(char *buf,int bmax); + bool reparent(); + void reset_wf_sliders(bool do_draw); + void set_mode(int tak,ScInfo&); + void set_ampl_txt(int); + void set_sampled_loc(int loc); + static void show_menu(Id); +}; + +struct KeybNote { + int lnr,snr,dur; + uint col:4, + sign:2; +}; + +struct KeybTune { + static const int keyb_notes_max=10000; + KeybNote buf[keyb_notes_max]; + int cur_ind, + turn_start, + tune_wid; + void reset(); + void nxt_turn(); + KeybTune(); + void add(int lnr,int sign,int snr1,int snr2,int col); +}; + +bool exec_info(int,Score*,bool); +void init_soundcard(); +CtrlBase *col2ctrl(int col); +PhmCtrl *col2phm_ctrl(int col); +struct Instrument *col2instr(int note_col); +void *wave_listen(void *arg); +bool play_wfile(float *buf_left,float *buf_right); +int same_color(ScSection *fst,ScSection *sec,ScSection *end,ScSection*& lst); +void restore_marked_sects(Score* sc,int play_start); +bool init_phmbuf(); +bool play(float *buf_left,float *buf_right); +void set_time_scale(); +void set_buffer_size(); +void reset_mk_nbuf(); + +extern KeybTune kb_tune; +extern bool mk_connected, + sample_rate_changed; +extern const char *midi_out_file, + *ps_out_file, + *wave_out_file; +extern const float ampl_mult[]; +extern int output_port; +extern struct JackInterf *jack_interface; diff --git a/src/str.cpp b/src/str.cpp new file mode 100644 index 0000000..8bcbbec --- /dev/null +++ b/src/str.cpp @@ -0,0 +1,178 @@ +#include +#include +#include "str.h" + +extern void alert(const char *form,...); + +const char* str_ch(const char *s,char ch) { // like strchr(), ch can be 0 + if (!ch) return 0; + for (;*s;s++) if (*s==ch) return s; + return 0; +} + +Str::Str():ch(0),cmt_ch(0) { s[0]=0; } + +Str::Str(const char *str):ch(0),cmt_ch(0) { + if (str) cpy(str); else s[0]=0; +} + +void Str::cpy(const char *str) { + int i=0; + if (str) + for (;str[i];++i) { + if (i>=str_max) { alert("cpy: buf overflow"); s[str_max-1]=0; return; } + s[i]=str[i]; + } + s[i]=0; +} + +void Str::cat(const char *str) { + int i=0,j=0; + if (str) { + for (;s[i];++i); + for (;str[j];++i,++j) { + if (i>=str_max) { alert("cat: buf overflow"); s[str_max-1]=0; return; } + s[i]=str[j]; + } + } + s[i]=0; +} + +char* Str::tos(int i) { sprintf(s,"%d",i); return s; } + +void Str::new_ext(const char *ext) { // new extension + char *p=strrchr(s,'.'); + if (p) *p=0; + cat(ext); +} + +char* Str::get_dir() { // xx/yy -> xx, non-destructive + char *p=strrchr(s,'/'); + if (p) { + static Str dir(s); + dir.s[p-s]=0; + return dir.s; + } + return 0; +} + +char* Str::strip_dir() { // xx/yy -> yy, non-destructive + char *p=strrchr(s,'/'); + if (p) return p+1; + return s; +} + +char* Str::get_ext() { + return strrchr(s,'.'); +} + +int Str::is_in(char*const* arr,int dim) { + for (int i=0;i=str_max) { alert("rword: buf overflow"); s[str_max-1]=0; return; } + s[i]=ch; ch=getc(in); + if (ch==EOF) break; + if (ch==cmt_ch) { + for(;ch=getc(in),ch!='\n';) if (ch==EOF) return; + s[i+1]=0; return; + } + } + s[i]=0; + if (str_ch(" \t",ch)) { + while (ch2=ch,ch=getc(in)) { + if (ch==EOF) return; + if (ch==cmt_ch) { + for (;ch=getc(in),ch!='\n';) if (ch==EOF) return; + return; + } + if (!str_ch(" \t",ch)) break; + } + if (!str_ch(delim,ch)) { ungetc(ch,in); ch=ch2; } + } +} + +void Str::strtok(const char* string,const char* delim,int& tpos) { + char *p=const_cast(string), + *q, + *stop; + if (static_cast(strlen(string)) < tpos) { + alert("strtok: string=\"%s\", index=%d",string,tpos); return; + } + s[0]=0; + for (p+=tpos;;++p) { + if (!*p) { + ch=0; return; + } + if (*p==cmt_ch) { + for(++p;;++p) { + if (!*p) { ch=0; return; } + if (*p=='\n') { tpos=p-string+1; ch='\n'; return; } + } + } + if (!str_ch(" \t",*p)) break; + } + q=s; + for (;*p && !str_ch(delim,*p);p++) { + *q=*p; + if (++q>=s+str_max) { alert("strtok: buf overflow"); ch=0; return; } + } + *q=0; + stop=p; + if (*p) { + if (str_ch(" \t",*p)) { + while (str_ch(" \t",*(++p))); + if (*p==cmt_ch) { + for(++p;;++p) { + if (!*p) { ch=0; return; } + if (*p=='\n') { tpos=p-string+1; ch='\n'; return; } + } + } + if (*p && !str_ch(delim,*p)) stop=p-1; + else stop=p; + } + tpos = (stop - string) + 1; + } + else tpos = p - string; + ch=*stop; +} + +#ifdef TEST +void alert(const char *form,...) { puts(form); } + +int main(int argc,char **argv) { + Str direc(argv[0]); + printf("dir: %s\n",direc.get_dir()); + FILE *in; + if ((in=fopen("str.tst","r"))==NULL) exit(1); + int n,pos; + Str str; + str.cmt_ch='#'; + for (;;) { + str.rword(in," \n;"); + if (str.ch==EOF) { puts("EOF"); break; } + printf("[%s][%c]\n",str.s,str.ch); + } + char tst_str[]=" een ja;nee\n8 # nee dus\n# nee\nend"; + for(pos=0;;) { + str.strtok(tst_str," \n;",pos); + printf("[%s][%c]\n",str.s,str.ch); + if (str.ch==0) { puts("EOL"); break; } + } +} +#endif diff --git a/src/str.h b/src/str.h new file mode 100644 index 0000000..98c61b9 --- /dev/null +++ b/src/str.h @@ -0,0 +1,21 @@ +const int str_max=100; + +struct Str { + char ch, // last read character + cmt_ch, // after this char: comment + s[str_max+1]; // 1 extra for closing 0 + Str(); + Str(const char*); + void cpy(const char*); + void cat(const char*); + void rword(FILE* in,const char *delim); + void strtok(const char*,const char *delim,int& pos); + char *tos(int); + void new_ext(const char *ext); + char *get_dir(); + char *strip_dir(); + char *get_ext(); + int is_in(char*const*,int dim); + bool operator==(const char*); + bool operator!=(const char*); +}; diff --git a/src/templates.h b/src/templates.h new file mode 100644 index 0000000..34b6425 --- /dev/null +++ b/src/templates.h @@ -0,0 +1,142 @@ +void alert(const char *form,...); +extern bool debug; + +template +struct Array { + T buf[dim]; + uint len() { return dim; } + uint get_index(T& memb) { + for (uint ind=0;ind=%d)",ind,dim);// if (debug) abort(); + return buf[0]; + } + void operator=(T *val) { for (uint i=0;i +struct SLList_elem { // single-linked list element + T d; + SLList_elem* nxt; + SLList_elem(T& d1):d(d1),nxt(0) { } + ~SLList_elem() { delete nxt; } +}; + +template // single-linked list +struct SLinkedList { + SLList_elem *lis; + SLinkedList() { lis=0; } + ~SLinkedList() { delete lis; } + void reset() { delete lis; lis=0; } + SLList_elem *insert(T elm, bool incr) { // if incr then increasing + SLList_elem *p,*p1, + *ret=0; + if (!lis) + lis=ret=new SLList_elem(elm); + else if (elm==lis->d); + else if ((incr && (elmd)) || (!incr && (lis->d(elm); + p1->nxt=lis; lis=p1; + } + else { + for (p=lis;;p=p->nxt) { + if (p->d==elm) break; + if (!p->nxt) { + p->nxt=ret=new SLList_elem(elm); + break; + } + if ((incr && (elm < p->nxt->d)) || (!incr && (p->nxt->d < elm))) { + p1=ret=new SLList_elem(elm); + p1->nxt=p->nxt; p->nxt=p1; + break; + } + } + } + return ret; + } + SLList_elem *prepend(T elm) { + SLList_elem *p, + *ret; + if (!lis) + lis=ret=new SLList_elem(elm); + else { + p=ret=new SLList_elem(elm); + p->nxt=lis; lis=p; + } + return ret; + } + void remove(T elm) { + SLList_elem *p,*prev; + if (!lis) return; + if (lis->d==elm) { + p=lis->nxt; lis->nxt=0; delete lis; lis=p; + return; + } + for (prev=lis,p=lis->nxt;p;) { + if (p->d==elm) { + prev->nxt=p->nxt; p->nxt=0; delete p; + return; + } + else { prev=p; p=p->nxt; } + } + puts("SLL: remove: elm not found"); + } + SLList_elem *remove(SLList_elem *p) { // returns next element + SLList_elem *p1,*prev; + if (!lis) { puts("SLL: lis=0"); return 0; } + if (lis==p) { + p1=lis->nxt; lis->nxt=0; delete lis; lis=p1; + return lis; + } + for (prev=lis,p1=lis->nxt;p1;) { + if (p==p1) { + prev->nxt=p1->nxt; p1->nxt=0; delete p1; + return prev->nxt; + } + prev=p1; p1=p1->nxt; + } + puts("SLL: remove: ptr not found"); + return 0; + } + void invert() { + SLList_elem *p,*prev,*next; + if (!lis || !lis->nxt) return; + for (prev=lis,p=lis->nxt,next=p->nxt,lis->nxt=0;;) { + p->nxt=prev; + prev=p; + if (!next) { lis=p; break; } + p=next; + next=p->nxt; + } + } +}; + +template +struct SafeBuffer { + bool alloced; // buf alloced? + int size, + bpos; // current buf index + T *buf; + SafeBuffer():alloced(false),size(0),bpos(0),buf(0) { } + SafeBuffer(SafeBuffer& src):alloced(false),size(src.size),bpos(0),buf(src.buf) { } + void operator=(SafeBuffer& src) { buf=src.buf; size=src.size; alloced=false; bpos=0; } + ~SafeBuffer() { if (alloced) delete[] buf; } + void reset() { + if (alloced) delete[] buf; + buf=0; size=0; bpos=0; alloced=false; + } +}; + +template T* re_alloc(T* arr,int& len) { + int i; + T* new_arr=new T[len*2]; + for (i=0;i +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "x-widgets.h" + +struct Color5 { + uint *c; + Color5():c(0) { } + void set_color(const char* c0,const char* c1,const char* c2,const char* c3,const char* c4) { + c=new uint[5]; + c[0]=calc_color(c0); c[1]=calc_color(c1); c[2]=calc_color(c2); c[3]=calc_color(c3); c[4]=calc_color(c4); + } +}; + +uint key_pressed, // keyboard event for top window + ctrl_key_pressed, + cBlack, cWhite, cGrey, cRed, cBlue, cRose, cButBground, cMenuBground, cCheckBox, + cBorder, cBackground, cForeground, cSelRBut, + nominal_font_size=10; +bool x_running; // false upto map_top_window(), true at start of run_xwindows() + +XftColor *xft_White,*xft_Black,*xft_Blue,*xft_Red; +cairo_pattern_t *cai_White, *cai_Black, *cai_Blue, *cai_Red, *cai_Border; + +Style button_style(0,0,0), + ext_rbut_style(0,0,0), + checkbox_style(0,0,0); + +SliderStyle slider_style(1,true); + +static Color5 cGradientBlue, cGradientRose, cGradientWheat, cGradientGreen, cGradientGrey; +static uint cSelCmdM, cSlBackgr, cPointer, cScrollbar, cScrollbar1, + go_ticks; +static const int + LBMAX=20, // labels etc. + MBDIST=18; // cmd menu buttons +static int + char_wid, + pixdepth; + +struct Repeat { + bool on; + XEvent ev; +} repeat; + +static GC + def_gc, // default + clip_gc; // clip mask +static XGCValues gcv; +static XGlyphInfo glyphinfo; +static XftFont *xft_def_font, + *xft_bold_font, + *xft_small_font, + *xft_mono_font; +static XWMHints wm_icon_hints; +static Display *dis; +static Visual *vis; +static Colormap cmap; +static uint screen, + root_window; +//Cursor cursor_xterm; +static Atom WM_DELETE_WINDOW, + WM_PROTOCOLS, + CLIPBOARD; + +static const char *lamp_pm[]={ +"12 12 4 1", +"# c #606060", +"a c #a0a0a0", +"b c #e0e0e0", +". c #ffffff", +"....##aa....", +"..####aaaa..", +".#####aaaaa.", +".#####aaaaa.", +"######aaaaaa", +"######aaaaaa", +"######bbbbbb", +"aaaaaabbbbbb", +".aaaaabbbbb.", +".aaaaabbbbb.", +"..aaaabbbb..", +"....aabb...." +}; + +static struct AlertWin *alert_win; + +struct TheCursor { + struct DialogWin *diawd; + struct EditWin *edwd; + void unset(); +} the_cursor; + +static pthread_mutex_t mtx= PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static pthread_t xwin_thread; + +/* This could be used in all drawing functions: + if (!pthread_equal(pthread_self(),xwin_thread)) puts("X event from other thread"); +*/ + +static int min(int a, int b) { return a<=b ? a : b; } +static int max(int a, int b) { return a>=b ? a : b; } +static int minmax(int a, int x, int b) { return x>=b ? b : x<=a ? a : x; } +static int divide(int a,int b) { return (2 * a + b)/b/2; } + +void err(const char *form,...) { + va_list ap; + va_start(ap,form); + printf("Error: "); + vprintf(form,ap); + va_end(ap); + putchar('\n'); + exit(1); +} + +void say(const char *form,...) { // for debugging + va_list ap; + va_start(ap,form); + printf("say: "); vprintf(form,ap); putchar('\n'); + va_end(ap); + fflush(stdout); +} + +Id::Id(int _id1):id1(_id1),id2(-1) { } +Id::Id(int _id1,int _id2):id1(_id1),id2(_id2) { } + +Label::Label(const char* t):txt(t),undersc(-1),draw(0) { } +Label::Label(const char* t,int us):txt(t),undersc(us),draw(0) { } +Label::Label(void (*dr)(uint win,XftDraw *xft_win,Id id,int par,int y_off)):txt(0),undersc(-1),draw(dr) { } +Label::Label(const char *t,void (*dr)(uint win,XftDraw *xft_win,Id id,int par,int y_off)):txt(t),undersc(-1),draw(dr) { } + +Style::Style(int _st,uint col,int par):st(_st),bgcol(col),param(par) { } +void Style::set(int _st,uint col,int par) { st=_st; bgcol=col; param=par; } + +SliderStyle::SliderStyle(int _st,bool gr):st(_st),grid(gr) { } +void SliderStyle::set(int _st,bool gr) { st=_st; grid=gr; } + +Pixmap2::Pixmap2():pm(0),cm(0) { } + +template T* re_alloc(T* arr,int& len,T ival) { + int i; + T* new_arr=new T[len*2]; + for (i=0;iwin; + textwin=new TextWin(awin,Rect(4,4,rect.width-10,rect.height-10),FN,dim); + } + static void del_cmd(Id) { + delete_window(alert_win->awin); + delete alert_win; alert_win=0; + } + ~AlertWin() { delete textwin; delete subw; } +}; + +void alert(const char *form,...) { + char buf[100]; + va_list ap; + va_start(ap,form); + vsnprintf(buf,100,form,ap); + va_end(ap); + if (dis) { + if (!alert_win) alert_win=new AlertWin(); + alert_win->textwin->print_text(buf); + } + else + puts(buf); +} + +template +struct WinBuf { + int win_nr, + wmax; + T **wbuf; + const char* type; + WinBuf(int wm,const char *t): + win_nr(-1), + wmax(wm), + wbuf(new T*[wmax]), + type(t) { + for (int i=0;i(wbuf,wmax,0); + return wbuf[++win_nr]; + } + bool in_a_win(Window win,T*& rbw) { + int i; + T *wd; + for (i=0;i<=win_nr;++i) { + wd=wbuf[i]; + if (win==wd->win) { rbw=wd; return true; } + } + return false; + } + void unlist_widget(T* wd) { // remove widget adress from wbuf[] + int i; + for (i=0;i<=win_nr;++i) { + if (wd==wbuf[i]) { + wbuf[i]=wbuf[win_nr]; + --win_nr; + return; + } + } + alert("unlist_widget: widget not found"); + } +}; + +WinBuf dia_winb(1,"DialogWin"); // dialogs +WinBuf t_winb(10,"TextWin"); // text windows +WinBuf