Initial commit
authorJames Bunton <jamesbunton@delx.net.au>
Sun, 21 Dec 2003 11:39:16 +0000 (11:39 +0000)
committerJames Bunton <jamesbunton@delx.net.au>
Sun, 21 Dec 2003 11:39:16 +0000 (11:39 +0000)
19 files changed:
COPYING.txt [new file with mode: 0644]
README.txt [new file with mode: 0644]
cello.png [new file with mode: 0644]
contrabass.png [new file with mode: 0644]
instrument.cpp [new file with mode: 0644]
instrument.h [new file with mode: 0644]
main.cpp [new file with mode: 0644]
mainwin.cpp [new file with mode: 0644]
mainwin.h [new file with mode: 0644]
midiengine.cpp [new file with mode: 0644]
midiengine.h [new file with mode: 0644]
piano.png [new file with mode: 0644]
pianoinstrument.cpp [new file with mode: 0644]
pianoinstrument.h [new file with mode: 0644]
stringinstrument.cpp [new file with mode: 0644]
stringinstrument.h [new file with mode: 0644]
viola.png [new file with mode: 0644]
violin.png [new file with mode: 0644]
vtones.pro [new file with mode: 0644]

diff --git a/COPYING.txt b/COPYING.txt
new file mode 100644 (file)
index 0000000..45645b4
--- /dev/null
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE\r
+                      Version 2, June 1991\r
+\r
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.\r
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+ Everyone is permitted to copy and distribute verbatim copies\r
+ of this license document, but changing it is not allowed.\r
+\r
+                           Preamble\r
+\r
+  The licenses for most software are designed to take away your\r
+freedom to share and change it.  By contrast, the GNU General Public\r
+License is intended to guarantee your freedom to share and change free\r
+software--to make sure the software is free for all its users.  This\r
+General Public License applies to most of the Free Software\r
+Foundation's software and to any other program whose authors commit to\r
+using it.  (Some other Free Software Foundation software is covered by\r
+the GNU Library General Public License instead.)  You can apply it to\r
+your programs, too.\r
+\r
+  When we speak of free software, we are referring to freedom, not\r
+price.  Our General Public Licenses are designed to make sure that you\r
+have the freedom to distribute copies of free software (and charge for\r
+this service if you wish), that you receive source code or can get it\r
+if you want it, that you can change the software or use pieces of it\r
+in new free programs; and that you know you can do these things.\r
+\r
+  To protect your rights, we need to make restrictions that forbid\r
+anyone to deny you these rights or to ask you to surrender the rights.\r
+These restrictions translate to certain responsibilities for you if you\r
+distribute copies of the software, or if you modify it.\r
+\r
+  For example, if you distribute copies of such a program, whether\r
+gratis or for a fee, you must give the recipients all the rights that\r
+you have.  You must make sure that they, too, receive or can get the\r
+source code.  And you must show them these terms so they know their\r
+rights.\r
+\r
+  We protect your rights with two steps: (1) copyright the software, and\r
+(2) offer you this license which gives you legal permission to copy,\r
+distribute and/or modify the software.\r
+\r
+  Also, for each author's protection and ours, we want to make certain\r
+that everyone understands that there is no warranty for this free\r
+software.  If the software is modified by someone else and passed on, we\r
+want its recipients to know that what they have is not the original, so\r
+that any problems introduced by others will not reflect on the original\r
+authors' reputations.\r
+\r
+  Finally, any free program is threatened constantly by software\r
+patents.  We wish to avoid the danger that redistributors of a free\r
+program will individually obtain patent licenses, in effect making the\r
+program proprietary.  To prevent this, we have made it clear that any\r
+patent must be licensed for everyone's free use or not licensed at all.\r
+\r
+  The precise terms and conditions for copying, distribution and\r
+modification follow.\r
+\f\r
+                   GNU GENERAL PUBLIC LICENSE\r
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\r
+\r
+  0. This License applies to any program or other work which contains\r
+a notice placed by the copyright holder saying it may be distributed\r
+under the terms of this General Public License.  The "Program", below,\r
+refers to any such program or work, and a "work based on the Program"\r
+means either the Program or any derivative work under copyright law:\r
+that is to say, a work containing the Program or a portion of it,\r
+either verbatim or with modifications and/or translated into another\r
+language.  (Hereinafter, translation is included without limitation in\r
+the term "modification".)  Each licensee is addressed as "you".\r
+\r
+Activities other than copying, distribution and modification are not\r
+covered by this License; they are outside its scope.  The act of\r
+running the Program is not restricted, and the output from the Program\r
+is covered only if its contents constitute a work based on the\r
+Program (independent of having been made by running the Program).\r
+Whether that is true depends on what the Program does.\r
+\r
+  1. You may copy and distribute verbatim copies of the Program's\r
+source code as you receive it, in any medium, provided that you\r
+conspicuously and appropriately publish on each copy an appropriate\r
+copyright notice and disclaimer of warranty; keep intact all the\r
+notices that refer to this License and to the absence of any warranty;\r
+and give any other recipients of the Program a copy of this License\r
+along with the Program.\r
+\r
+You may charge a fee for the physical act of transferring a copy, and\r
+you may at your option offer warranty protection in exchange for a fee.\r
+\r
+  2. You may modify your copy or copies of the Program or any portion\r
+of it, thus forming a work based on the Program, and copy and\r
+distribute such modifications or work under the terms of Section 1\r
+above, provided that you also meet all of these conditions:\r
+\r
+    a) You must cause the modified files to carry prominent notices\r
+    stating that you changed the files and the date of any change.\r
+\r
+    b) You must cause any work that you distribute or publish, that in\r
+    whole or in part contains or is derived from the Program or any\r
+    part thereof, to be licensed as a whole at no charge to all third\r
+    parties under the terms of this License.\r
+\r
+    c) If the modified program normally reads commands interactively\r
+    when run, you must cause it, when started running for such\r
+    interactive use in the most ordinary way, to print or display an\r
+    announcement including an appropriate copyright notice and a\r
+    notice that there is no warranty (or else, saying that you provide\r
+    a warranty) and that users may redistribute the program under\r
+    these conditions, and telling the user how to view a copy of this\r
+    License.  (Exception: if the Program itself is interactive but\r
+    does not normally print such an announcement, your work based on\r
+    the Program is not required to print an announcement.)\r
+\f\r
+These requirements apply to the modified work as a whole.  If\r
+identifiable sections of that work are not derived from the Program,\r
+and can be reasonably considered independent and separate works in\r
+themselves, then this License, and its terms, do not apply to those\r
+sections when you distribute them as separate works.  But when you\r
+distribute the same sections as part of a whole which is a work based\r
+on the Program, the distribution of the whole must be on the terms of\r
+this License, whose permissions for other licensees extend to the\r
+entire whole, and thus to each and every part regardless of who wrote it.\r
+\r
+Thus, it is not the intent of this section to claim rights or contest\r
+your rights to work written entirely by you; rather, the intent is to\r
+exercise the right to control the distribution of derivative or\r
+collective works based on the Program.\r
+\r
+In addition, mere aggregation of another work not based on the Program\r
+with the Program (or with a work based on the Program) on a volume of\r
+a storage or distribution medium does not bring the other work under\r
+the scope of this License.\r
+\r
+  3. You may copy and distribute the Program (or a work based on it,\r
+under Section 2) in object code or executable form under the terms of\r
+Sections 1 and 2 above provided that you also do one of the following:\r
+\r
+    a) Accompany it with the complete corresponding machine-readable\r
+    source code, which must be distributed under the terms of Sections\r
+    1 and 2 above on a medium customarily used for software interchange; or,\r
+\r
+    b) Accompany it with a written offer, valid for at least three\r
+    years, to give any third party, for a charge no more than your\r
+    cost of physically performing source distribution, a complete\r
+    machine-readable copy of the corresponding source code, to be\r
+    distributed under the terms of Sections 1 and 2 above on a medium\r
+    customarily used for software interchange; or,\r
+\r
+    c) Accompany it with the information you received as to the offer\r
+    to distribute corresponding source code.  (This alternative is\r
+    allowed only for noncommercial distribution and only if you\r
+    received the program in object code or executable form with such\r
+    an offer, in accord with Subsection b above.)\r
+\r
+The source code for a work means the preferred form of the work for\r
+making modifications to it.  For an executable work, complete source\r
+code means all the source code for all modules it contains, plus any\r
+associated interface definition files, plus the scripts used to\r
+control compilation and installation of the executable.  However, as a\r
+special exception, the source code distributed need not include\r
+anything that is normally distributed (in either source or binary\r
+form) with the major components (compiler, kernel, and so on) of the\r
+operating system on which the executable runs, unless that component\r
+itself accompanies the executable.\r
+\r
+If distribution of executable or object code is made by offering\r
+access to copy from a designated place, then offering equivalent\r
+access to copy the source code from the same place counts as\r
+distribution of the source code, even though third parties are not\r
+compelled to copy the source along with the object code.\r
+\f\r
+  4. You may not copy, modify, sublicense, or distribute the Program\r
+except as expressly provided under this License.  Any attempt\r
+otherwise to copy, modify, sublicense or distribute the Program is\r
+void, and will automatically terminate your rights under this License.\r
+However, parties who have received copies, or rights, from you under\r
+this License will not have their licenses terminated so long as such\r
+parties remain in full compliance.\r
+\r
+  5. You are not required to accept this License, since you have not\r
+signed it.  However, nothing else grants you permission to modify or\r
+distribute the Program or its derivative works.  These actions are\r
+prohibited by law if you do not accept this License.  Therefore, by\r
+modifying or distributing the Program (or any work based on the\r
+Program), you indicate your acceptance of this License to do so, and\r
+all its terms and conditions for copying, distributing or modifying\r
+the Program or works based on it.\r
+\r
+  6. Each time you redistribute the Program (or any work based on the\r
+Program), the recipient automatically receives a license from the\r
+original licensor to copy, distribute or modify the Program subject to\r
+these terms and conditions.  You may not impose any further\r
+restrictions on the recipients' exercise of the rights granted herein.\r
+You are not responsible for enforcing compliance by third parties to\r
+this License.\r
+\r
+  7. If, as a consequence of a court judgment or allegation of patent\r
+infringement or for any other reason (not limited to patent issues),\r
+conditions are imposed on you (whether by court order, agreement or\r
+otherwise) that contradict the conditions of this License, they do not\r
+excuse you from the conditions of this License.  If you cannot\r
+distribute so as to satisfy simultaneously your obligations under this\r
+License and any other pertinent obligations, then as a consequence you\r
+may not distribute the Program at all.  For example, if a patent\r
+license would not permit royalty-free redistribution of the Program by\r
+all those who receive copies directly or indirectly through you, then\r
+the only way you could satisfy both it and this License would be to\r
+refrain entirely from distribution of the Program.\r
+\r
+If any portion of this section is held invalid or unenforceable under\r
+any particular circumstance, the balance of the section is intended to\r
+apply and the section as a whole is intended to apply in other\r
+circumstances.\r
+\r
+It is not the purpose of this section to induce you to infringe any\r
+patents or other property right claims or to contest validity of any\r
+such claims; this section has the sole purpose of protecting the\r
+integrity of the free software distribution system, which is\r
+implemented by public license practices.  Many people have made\r
+generous contributions to the wide range of software distributed\r
+through that system in reliance on consistent application of that\r
+system; it is up to the author/donor to decide if he or she is willing\r
+to distribute software through any other system and a licensee cannot\r
+impose that choice.\r
+\r
+This section is intended to make thoroughly clear what is believed to\r
+be a consequence of the rest of this License.\r
+\f\r
+  8. If the distribution and/or use of the Program is restricted in\r
+certain countries either by patents or by copyrighted interfaces, the\r
+original copyright holder who places the Program under this License\r
+may add an explicit geographical distribution limitation excluding\r
+those countries, so that distribution is permitted only in or among\r
+countries not thus excluded.  In such case, this License incorporates\r
+the limitation as if written in the body of this License.\r
+\r
+  9. The Free Software Foundation may publish revised and/or new versions\r
+of the General Public License from time to time.  Such new versions will\r
+be similar in spirit to the present version, but may differ in detail to\r
+address new problems or concerns.\r
+\r
+Each version is given a distinguishing version number.  If the Program\r
+specifies a version number of this License which applies to it and "any\r
+later version", you have the option of following the terms and conditions\r
+either of that version or of any later version published by the Free\r
+Software Foundation.  If the Program does not specify a version number of\r
+this License, you may choose any version ever published by the Free Software\r
+Foundation.\r
+\r
+  10. If you wish to incorporate parts of the Program into other free\r
+programs whose distribution conditions are different, write to the author\r
+to ask for permission.  For software which is copyrighted by the Free\r
+Software Foundation, write to the Free Software Foundation; we sometimes\r
+make exceptions for this.  Our decision will be guided by the two goals\r
+of preserving the free status of all derivatives of our free software and\r
+of promoting the sharing and reuse of software generally.\r
+\r
+                           NO WARRANTY\r
+\r
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\r
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\r
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\r
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\r
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\r
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\r
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\r
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\r
+REPAIR OR CORRECTION.\r
+\r
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\r
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\r
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\r
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\r
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\r
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\r
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\r
+POSSIBILITY OF SUCH DAMAGES.\r
+\r
+                    END OF TERMS AND CONDITIONS\r
+\f\r
+           How to Apply These Terms to Your New Programs\r
+\r
+  If you develop a new program, and you want it to be of the greatest\r
+possible use to the public, the best way to achieve this is to make it\r
+free software which everyone can redistribute and change under these terms.\r
+\r
+  To do so, attach the following notices to the program.  It is safest\r
+to attach them to the start of each source file to most effectively\r
+convey the exclusion of warranty; and each file should have at least\r
+the "copyright" line and a pointer to where the full notice is found.\r
+\r
+    <one line to give the program's name and a brief idea of what it does.>\r
+    Copyright (C) <year>  <name of author>\r
+\r
+    This program is free software; you can redistribute it and/or modify\r
+    it under the terms of the GNU General Public License as published by\r
+    the Free Software Foundation; either version 2 of the License, or\r
+    (at your option) any later version.\r
+\r
+    This program is distributed in the hope that it will be useful,\r
+    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+    GNU General Public License for more details.\r
+\r
+    You should have received a copy of the GNU General Public License\r
+    along with this program; if not, write to the Free Software\r
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
+\r
+\r
+Also add information on how to contact you by electronic and paper mail.\r
+\r
+If the program is interactive, make it output a short notice like this\r
+when it starts in an interactive mode:\r
+\r
+    Gnomovision version 69, Copyright (C) year name of author\r
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r
+    This is free software, and you are welcome to redistribute it\r
+    under certain conditions; type `show c' for details.\r
+\r
+The hypothetical commands `show w' and `show c' should show the appropriate\r
+parts of the General Public License.  Of course, the commands you use may\r
+be called something other than `show w' and `show c'; they could even be\r
+mouse-clicks or menu items--whatever suits your program.\r
+\r
+You should also get your employer (if you work as a programmer) or your\r
+school, if any, to sign a "copyright disclaimer" for the program, if\r
+necessary.  Here is a sample; alter the names:\r
+\r
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\r
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.\r
+\r
+  <signature of Ty Coon>, 1 April 1989\r
+  Ty Coon, President of Vice\r
+\r
+This General Public License does not permit incorporating your program into\r
+proprietary programs.  If your program is a subroutine library, you may\r
+consider it more useful to permit linking proprietary applications with the\r
+library.  If this is what you want to do, use the GNU Library General\r
+Public License instead of this License.\r
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..58eba3b
--- /dev/null
@@ -0,0 +1,54 @@
+About
+-----
+
+This program was written by James Bunton <james@delx.cjb.net>
+It is licensed under the terms of the GNU GPL version 2
+(see COPYING.txt for more details)
+
+This program was written for Julieanne.. :-)
+Hope you like your Christmas present!
+
+If you have any feature requests, bug reports, etc, send them to me.
+Thanks and enjoy..
+
+
+
+
+Instructions
+------------
+
+If you are using Windows 95 or later, you need a MIDI device set up.
+If running VTone.exe does not work, go to
+Start->Control Panel->Multimedia and select a synthesiser.
+
+
+If you are using Linux, make sure you have a sequencer set up.
+Timidity++ is a good one. Kill any sound servers you have running,
+and then start Timidity in ALSA client mode (for this you need
+ALSA sound drivers obviously)
+
+killall artsd
+timidity -iA -B2,8 -Os -EFreverb=0
+
+That usually does the trick.
+
+
+
+TODO
+----
+
+'Clean' the MIDI files so that notes are saved as with the exact lengths
+of crotchets, quavers, etc. This means opening the file in a notation
+editor, such as Finale or Rosegarden would give better results.
+
+
+
+BUGS
+----
+
+* It is impossible to push certain combinations of keys.
+    (A problem with the OS, or keyboard hardware?)
+
+* Contrabass interface is a bit weird, but works mostly ok
+
+
diff --git a/cello.png b/cello.png
new file mode 100644 (file)
index 0000000..71c4f0a
Binary files /dev/null and b/cello.png differ
diff --git a/contrabass.png b/contrabass.png
new file mode 100644 (file)
index 0000000..030d6ba
Binary files /dev/null and b/contrabass.png differ
diff --git a/instrument.cpp b/instrument.cpp
new file mode 100644 (file)
index 0000000..f8d7083
--- /dev/null
@@ -0,0 +1,115 @@
+// instrument.cpp - An instrument widget
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#include "instrument.h"
+
+
+Instrument::Instrument(QWidget *parent)
+: QWidget(parent, 0)
+{
+       // Grab focus
+//     grabKeyboard();
+       setFocusPolicy(StrongFocus);
+       setFocus();
+
+       noteStart = 0;
+
+       // Initialise the base midi notes array
+       midnotes[0]  = QString("C");
+       midnotes[1]  = QString("C#");
+       midnotes[2]  = QString("D");
+       midnotes[3]  = QString("D#");
+       midnotes[4]  = QString("E");
+       midnotes[5]  = QString("F");
+       midnotes[6]  = QString("F#");
+       midnotes[7]  = QString("G");
+       midnotes[8]  = QString("G#");
+       midnotes[9]  = QString("A");
+       midnotes[10] = QString("A#");
+       midnotes[11] = QString("B");
+}
+
+Instrument::~Instrument()
+{
+       releaseKeyboard();
+}
+
+void Instrument::displayHelp()
+{
+       QMessageBox::information(this, "Help", generateHelp());
+}
+
+bool Instrument::setNoteStart(int note)
+{
+       if(note % 12 == 0 && noteStart >= 0 && noteStart <= 127) {
+               noteStart = note;
+               return true;
+       }
+       else {
+//             printf("noteStart=%d is invalid\n", note);
+               return false;
+       }
+}
+
+int Instrument::getNoteStart()
+{
+       return noteStart;
+}
+
+void Instrument::setStartOctave(int octave)
+{
+       if(setNoteStart((octave) * 12) == false) {
+               QMessageBox::warning(parentWidget(), "Error", "Could not set octave. This shouldn't happen!");
+       }
+}
+
+void Instrument::focusOutEvent(QFocusEvent *)
+{
+       setFocus();
+}
+
+bool Instrument::event(QEvent *e)
+{
+       if(e->type() == QEvent::KeyPress) {
+               QKeyEvent *k = (QKeyEvent *)e;
+               keyPressEvent(k);
+               if(k->isAccepted() == true) {
+                       return true;
+               }
+       }
+       else if(e->type() == QEvent::KeyRelease) {
+               QKeyEvent *k = (QKeyEvent *)e;
+               keyReleaseEvent(k);
+               if(k->isAccepted() == true) {
+                       return true;
+               }
+       }
+
+       return QWidget::event(e);
+}
+
+QString Instrument::midi2string(int num)
+{
+       int i;
+       for(i = 9; num > 11; i--) {
+               num -= 12;
+       }
+
+       return QString(midnotes[num] + " - Octave:" + QString::number(i - 6)); // Middle C is then "C - Octave: 3"
+}
+
+bool Instrument::checkSharp(int note)
+{
+//1    13      25      37      49      61      73      85      97      109     121
+//6    18      30      42      54      66      78      90      102     114     126
+       if((note - 1) % 12 == 0 || note - 1 == 0)
+               return true;
+       if((note - 6) % 12 == 0 || note - 6 == 0)
+               return true;
+
+       return false;
+}
+
+
diff --git a/instrument.h b/instrument.h
new file mode 100644 (file)
index 0000000..6869a59
--- /dev/null
@@ -0,0 +1,57 @@
+// instrument.h - An instrument widget
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#ifndef INSTRUMENT_H
+#define INSTRUMENT_H
+
+#include <qwidget.h>
+#include <qevent.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qmessagebox.h>
+\r
+\r
+#if QT_VERSION < 300\r
+#define setPaletteBackgroundPixmap setBackgroundPixmap\r
+#endif\r
+
+
+class Instrument : public QWidget
+{
+Q_OBJECT
+       public:
+               Instrument(QWidget *parent);
+               ~Instrument();
+
+               bool setNoteStart(int note);
+               int getNoteStart();
+
+       public slots:
+               void setStartOctave(int octave); // Middle C is in octave 5
+               void displayHelp();
+
+       protected:
+               virtual QString generateHelp()=0;
+               void focusOutEvent(QFocusEvent *);
+               bool event(QEvent *e);
+               QString midi2string(int num);
+               bool checkSharp(int num);
+               int noteStart;
+
+       private:
+               // The base midi notes
+               QString midnotes[12];
+
+               // Make the function pure virtual
+               virtual void emitSounds()=0;
+
+       signals:
+               void playNote(int, int, int); // Note number, volume, length
+               void stopNote(int); // Note number
+};
+
+
+
+#endif
diff --git a/main.cpp b/main.cpp
new file mode 100644 (file)
index 0000000..f4f9bcf
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,16 @@
+// main.cpp - MIDI keyboard program
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+#include <qapplication.h>
+#include "mainwin.h"
+
+int main(int argc, char **argv)
+{
+       QApplication a(argc, argv);
+       MainWin w;
+       a.setMainWidget(&w);
+       return a.exec();
+}
+
+
diff --git a/mainwin.cpp b/mainwin.cpp
new file mode 100644 (file)
index 0000000..d7796c9
--- /dev/null
@@ -0,0 +1,374 @@
+// mainwin.cpp - Displays the MIDI keyboard and instrument selector
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#include "mainwin.h"
+
+
+MainWin::MainWin()
+: QWidget(0,0)
+{
+       setCaption("Virtual Tones");
+
+       // Setup the MIDI output
+       midi = new MidiReal();
+       if(midi->initSuccess() == false) {
+               QMessageBox::critical(this, "MIDI Error", midi->getError());
+               qApp->quit();
+               exit(1);
+       }
+       midiFile = 0;
+
+       // Create the sound selector
+       soundSelection = new QComboBox(false, this);
+       QToolTip::add(soundSelection, "Select the MIDI instrument to play with");
+       fillSounds();
+       connect(soundSelection, SIGNAL(activated(int)), midi, SLOT(setInstrument(int)));
+       soundSelectionLabel = new QLabel("Sound:", this);
+
+       // Setup the interface selector
+       interfaceSelection = new QComboBox(false, this);
+       QToolTip::add(interfaceSelection, "Select the interface to play with");
+       interfaceSelection->insertItem("Piano", 0);
+       interfaceSelection->insertItem("Violin", 1);
+       interfaceSelection->insertItem("Viola", 2);
+       interfaceSelection->insertItem("Cello", 3);
+       interfaceSelection->insertItem("Contrabass", 4);
+       connect(interfaceSelection, SIGNAL(activated(int)), this, SLOT(interfaceSelectionSlot(int)));
+       instrument = 0;
+       interfaceSelectionLabel = new QLabel("Interface:", this);
+
+       // Setup the octave selector
+       octaveSelection = new QComboBox(false, this);
+       QToolTip::add(octaveSelection, "Select the starting octave");
+       for(int i = 0; i < 11; i++) {
+               octaveSelection->insertItem("Octave " + QString::number(i - 2), i);
+       }
+       octaveSelectionLabel = new QLabel("Base Octave:", this);
+
+       // Create the help & quit button
+       helpBtn = new QPushButton("Help", this);
+       quitBtn = new QPushButton("Quit", this);
+       connect(quitBtn, SIGNAL(clicked()), qApp, SLOT(quit()));
+
+       // MIDI recording buttons
+       recordBtn = new QPushButton("Record", this);
+       QToolTip::add(recordBtn, "Starts a MIDI recording of the notes played");
+       recordBtn->setEnabled(true);
+       connect(recordBtn, SIGNAL(clicked()), SLOT(setupRecord()));
+       stopBtn = new QPushButton("Stop", this);
+       QToolTip::add(stopBtn, "Stops the MIDI recording");
+       stopBtn->setEnabled(false);
+       connect(stopBtn, SIGNAL(clicked()), SLOT(finishRecord()));
+
+
+       // The about label
+       aboutLabel = new QLabel("Written for Julieanne...", this);
+
+       // Setup the layouts
+       soundSelectionLayout = new QVBoxLayout();
+       soundSelectionLayout->addWidget(soundSelectionLabel);
+       soundSelectionLayout->addWidget(soundSelection);
+
+       interfaceSelectionLayout = new QVBoxLayout();
+       interfaceSelectionLayout->addWidget(interfaceSelectionLabel);
+       interfaceSelectionLayout->addWidget(interfaceSelection);
+
+       octaveSelectionLayout = new QVBoxLayout();
+       octaveSelectionLayout->addWidget(octaveSelectionLabel);
+       octaveSelectionLayout->addWidget(octaveSelection);
+
+       midiBtnLayout = new QVBoxLayout();
+       midiBtnLayout->addWidget(recordBtn);
+       midiBtnLayout->addSpacing(5);
+       midiBtnLayout->addWidget(stopBtn);
+
+       buttonsLayout = new QVBoxLayout();
+       buttonsLayout->addWidget(helpBtn);
+       buttonsLayout->addSpacing(5);
+       buttonsLayout->addWidget(quitBtn);
+
+       optionsLayout = new QHBoxLayout();
+       optionsLayout->addSpacing(5);
+       optionsLayout->addLayout(interfaceSelectionLayout);
+       optionsLayout->addSpacing(5);
+       optionsLayout->addLayout(soundSelectionLayout);
+       optionsLayout->addSpacing(5);
+       optionsLayout->addLayout(octaveSelectionLayout);
+       optionsLayout->addSpacing(5);
+       optionsLayout->addLayout(midiBtnLayout);
+       optionsLayout->addSpacing(5);
+       optionsLayout->addLayout(buttonsLayout);
+       optionsLayout->addSpacing(5);
+
+       vLayout = new QVBoxLayout(this);
+       vLayout->insertSpacing(0, 5);
+       vLayout->insertLayout(1, optionsLayout);
+       vLayout->insertSpacing(2, 5);
+       vLayout->insertSpacing(3, 1);// instrument goes here
+       vLayout->insertSpacing(4, 5);
+       vLayout->insertWidget(5, aboutLabel);
+       vLayout->insertSpacing(6, 5);
+
+       interfaceSelectionSlot(0);
+
+       show();
+}
+
+MainWin::~MainWin()
+{
+       delete midi;
+       if(midiFile != 0)
+               delete midiFile;
+}
+
+void MainWin::setupRecord() {
+       if(midiFile == 0) {
+               QString filename = QFileDialog::getSaveFileName(QString::null, "MIDI files (*.mid *.midi)", this);
+               if(filename.isNull() || filename.isEmpty())
+                       return;
+               midiFile = new MidiFile(filename);
+               if(midiFile->initSuccess() == false) {
+                       QMessageBox::warning(this, "MIDI recording error", midiFile->getError());
+                       delete midiFile;
+                       return;
+               }
+               connect(soundSelection, SIGNAL(activated(int)), midiFile, SLOT(setInstrument(int)));
+               connect(instrument, SIGNAL(playNote(int, int, int)), midiFile, SLOT(playNote(int, int, int)));
+               connect(instrument, SIGNAL(stopNote(int)), midiFile, SLOT(stopNote(int)));
+       }
+
+       recordBtn->setEnabled(false);
+       stopBtn->setEnabled(true);
+}
+
+void MainWin::finishRecord() {
+       if(midiFile != 0) {
+               delete midiFile;
+               midiFile = 0;
+       }
+
+       recordBtn->setEnabled(true);
+       stopBtn->setEnabled(false);
+}
+
+void MainWin::interfaceSelectionSlot(int num)
+{
+       // Right now interfaces 0-4 exist
+
+       if(instrument != 0) {
+//             if(vLayout->findWidget(instrument)) {
+//                     vLayout->remove(instrument);
+//             }
+               delete instrument;
+               instrument = 0;
+       }
+
+       switch(num) {
+       // Create the instrument
+
+       default:
+       case 0: {
+               PianoInstrument *p = new PianoInstrument(this);
+               instrument = p;
+               // Select piano as the default
+               soundSelection->setCurrentItem(0);
+               midi->setInstrument(0);
+               if(midiFile != 0)
+                       midiFile->setInstrument(0);
+               break;
+       }
+       case 1: {
+               ViolinInstrument *p = new ViolinInstrument(this);
+               instrument = p;
+               // Select violin as the default
+               soundSelection->setCurrentItem(40);
+               midi->setInstrument(40);
+               if(midiFile != 0)
+                       midiFile->setInstrument(40);
+               break;
+       }
+       case 2: {
+               ViolaInstrument *p = new ViolaInstrument(this);
+               instrument = p;
+               // Select violin as the default
+               soundSelection->setCurrentItem(41);
+               midi->setInstrument(41);
+               if(midiFile != 0)
+                       midiFile->setInstrument(41);
+               break;
+       }
+       case 3: {
+               CelloInstrument *p = new CelloInstrument(this);
+               instrument = p;
+               // Select violin as the default
+               soundSelection->setCurrentItem(42);
+               midi->setInstrument(42);
+               if(midiFile != 0)
+                       midiFile->setInstrument(42);
+               break;
+       }
+       case 4: {
+               ContrabassInstrument *p = new ContrabassInstrument(this);
+               instrument = p;
+               // Select violin as the default
+               soundSelection->setCurrentItem(43);
+               midi->setInstrument(43);
+               if(midiFile != 0)
+                       midiFile->setInstrument(43);
+               break;
+       }
+       }
+
+       // Relayout
+       vLayout->insertWidget(3, instrument);
+       instrument->show();
+
+       // Connect signals
+       connect(instrument, SIGNAL(playNote(int, int, int)), midi, SLOT(playNote(int, int, int)));
+       connect(instrument, SIGNAL(stopNote(int)), midi, SLOT(stopNote(int)));
+       if(midiFile != 0) {
+               connect(instrument, SIGNAL(playNote(int, int, int)), midiFile, SLOT(playNote(int, int, int)));
+               connect(instrument, SIGNAL(stopNote(int)), midiFile, SLOT(stopNote(int)));
+       }
+       connect(helpBtn, SIGNAL(clicked()), instrument, SLOT(displayHelp()));
+       connect(octaveSelection, SIGNAL(activated(int)), instrument, SLOT(setStartOctave(int)));
+
+       // Set the octave widget
+       octaveSelection->setCurrentItem(instrument->getNoteStart() / 12);
+}
+
+void MainWin::fillSounds()
+{
+       soundSelection->insertItem("Acoustic Grand Piano", 0);
+       soundSelection->insertItem("Bright Acoustic Piano", 1);
+       soundSelection->insertItem("Electric Grand Piano", 2);
+       soundSelection->insertItem("Honky-tonk Piano", 3);
+       soundSelection->insertItem("Rhodes Piano", 4);
+       soundSelection->insertItem("Chorused Piano", 5);
+       soundSelection->insertItem("Harpsichord", 6);
+       soundSelection->insertItem("Clavinet", 7);
+       soundSelection->insertItem("Celesta", 8);
+       soundSelection->insertItem("Glockenspiel", 9);
+       soundSelection->insertItem("Music Box", 10);
+       soundSelection->insertItem("Vibraphone", 11);
+       soundSelection->insertItem("Marimba", 12);
+       soundSelection->insertItem("Xylophone", 13);
+       soundSelection->insertItem("Tubular bells", 14);
+       soundSelection->insertItem("Dulcimer", 15);
+       soundSelection->insertItem("Draw Organ", 16);
+       soundSelection->insertItem("Percussive Organ", 17);
+       soundSelection->insertItem("Rock Organ", 18);
+       soundSelection->insertItem("Church Organ", 19);
+       soundSelection->insertItem("Reed Organ", 20);
+       soundSelection->insertItem("Accordion", 21);
+       soundSelection->insertItem("Harmonica", 22);
+       soundSelection->insertItem("Tango Accordion", 23);
+       soundSelection->insertItem("Acoustic Nylon Guitar", 24);
+       soundSelection->insertItem("Acoustic Steel Guitar", 25);
+       soundSelection->insertItem("Electric Jazz Guitar", 26);
+       soundSelection->insertItem("Electric clean Guitar", 27);
+       soundSelection->insertItem("Electric Guitar muted", 28);
+       soundSelection->insertItem("Overdriven Guitar", 29);
+       soundSelection->insertItem("Distortion Guitar", 30);
+       soundSelection->insertItem("Guitar Harmonics", 31);
+       soundSelection->insertItem("Wood Bass", 32);
+       soundSelection->insertItem("Electric Bass Fingered", 33);
+       soundSelection->insertItem("Electric Bass Picked", 34);
+       soundSelection->insertItem("Fretless Bass", 35);
+       soundSelection->insertItem("Slap Bass 1", 36);
+       soundSelection->insertItem("Slap Bass 2", 37);
+       soundSelection->insertItem("Synth Bass 1", 38);
+       soundSelection->insertItem("Synth Bass 2", 39);
+       soundSelection->insertItem("Violin", 40);
+       soundSelection->insertItem("Viola", 41);
+       soundSelection->insertItem("Cello", 42);
+       soundSelection->insertItem("Contrabass", 43);
+       soundSelection->insertItem("Tremolo Strings", 44);
+       soundSelection->insertItem("Pizzicato Strings", 45);
+       soundSelection->insertItem("Orchestral Harp", 46);
+       soundSelection->insertItem("Timpani", 47);
+       soundSelection->insertItem("Acoustic String Ensemble 1", 48);
+       soundSelection->insertItem("Acoustic String Ensemble 2", 49);
+       soundSelection->insertItem("Synth Strings 1", 50);
+       soundSelection->insertItem("Synth Strings 2", 51);
+       soundSelection->insertItem("Aah Choir", 52);
+       soundSelection->insertItem("Ooh Choir", 53);
+       soundSelection->insertItem("Synvox", 54);
+       soundSelection->insertItem("Orchestra Hit", 55);
+       soundSelection->insertItem("Trumpet", 56);
+       soundSelection->insertItem("Trombone", 57);
+       soundSelection->insertItem("Tuba", 58);
+       soundSelection->insertItem("Muted Trumpet", 59);
+       soundSelection->insertItem("French Horn", 60);
+       soundSelection->insertItem("Brass Section", 61);
+       soundSelection->insertItem("Synth Brass 1", 62);
+       soundSelection->insertItem("Synth Brass 2", 63);
+       soundSelection->insertItem("Soprano Sax", 64);
+       soundSelection->insertItem("Alto Sax", 65);
+       soundSelection->insertItem("Tenor Sax", 66);
+       soundSelection->insertItem("Baritone Sax", 67);
+       soundSelection->insertItem("Oboe", 68);
+       soundSelection->insertItem("English Horn", 69);
+       soundSelection->insertItem("Bassoon", 70);
+       soundSelection->insertItem("Clarinet", 71);
+       soundSelection->insertItem("Piccolo", 72);
+       soundSelection->insertItem("Flute", 73);
+       soundSelection->insertItem("Recorder", 74);
+       soundSelection->insertItem("Pan Flute", 75);
+       soundSelection->insertItem("Bottle blow", 76);
+       soundSelection->insertItem("Shakuhachi", 77);
+       soundSelection->insertItem("Whistle", 78);
+       soundSelection->insertItem("Ocarina", 79);
+       soundSelection->insertItem("Square Lead", 80);
+       soundSelection->insertItem("Saw Lead", 81);
+       soundSelection->insertItem("Calliope", 82);
+       soundSelection->insertItem("Chiffer", 83);
+       soundSelection->insertItem("Synth Lead 5", 84);
+       soundSelection->insertItem("Synth Lead 6", 85);
+       soundSelection->insertItem("Synth Lead 7", 86);
+       soundSelection->insertItem("Synth Lead 8", 87);
+       soundSelection->insertItem("Synth Pad 1", 88);
+       soundSelection->insertItem("Synth Pad 2", 89);
+       soundSelection->insertItem("Synth Pad 3", 90);
+       soundSelection->insertItem("Synth Pad 4", 91);
+       soundSelection->insertItem("Synth Pad 5", 92);
+       soundSelection->insertItem("Synth Pad 6", 93);
+       soundSelection->insertItem("Synth Pad 7", 94);
+       soundSelection->insertItem("Synth Pad 8", 95);
+       soundSelection->insertItem("Ice Rain", 96);
+       soundSelection->insertItem("Soundtracks", 97);
+       soundSelection->insertItem("Crystal", 98);
+       soundSelection->insertItem("Atmosphere", 99);
+       soundSelection->insertItem("Bright", 100);
+       soundSelection->insertItem("Goblin", 101);
+       soundSelection->insertItem("Echoes", 102);
+       soundSelection->insertItem("Space", 103);
+       soundSelection->insertItem("Sitar", 104);
+       soundSelection->insertItem("Banjo", 105);
+       soundSelection->insertItem("Shamisen", 106);
+       soundSelection->insertItem("Koto", 107);
+       soundSelection->insertItem("Kalimba", 108);
+       soundSelection->insertItem("Bagpipe", 109);
+       soundSelection->insertItem("Fiddle", 110);
+       soundSelection->insertItem("Shanai", 111);
+       soundSelection->insertItem("Tinkle bell", 112);
+       soundSelection->insertItem("Agogo", 113);
+       soundSelection->insertItem("Steel Drums", 114);
+       soundSelection->insertItem("Woodblock", 115);
+       soundSelection->insertItem("Taiko Drum", 116);
+       soundSelection->insertItem("Melodic Tom", 117);
+       soundSelection->insertItem("Synth Tom", 118);
+       soundSelection->insertItem("Reverse Cymbal", 119);
+       soundSelection->insertItem("Guitar Fret Noise", 120);
+       soundSelection->insertItem("Breath Noise", 121);
+       soundSelection->insertItem("Seashore", 122);
+       soundSelection->insertItem("Bird Tweet", 123);
+       soundSelection->insertItem("Telephone Ring", 124);
+       soundSelection->insertItem("Helicopter", 125);
+       soundSelection->insertItem("Applause", 126);
+       soundSelection->insertItem("Gunshot", 127);
+
+}
+
diff --git a/mainwin.h b/mainwin.h
new file mode 100644 (file)
index 0000000..d1ee828
--- /dev/null
+++ b/mainwin.h
@@ -0,0 +1,76 @@
+// mainwin.h - Displays the MIDI keyboard and instrument selector
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+
+#ifndef MAINWIN_H
+#define MAINWIN_H
+
+#include <qapplication.h>
+#include <qmessagebox.h>
+#include <qcombobox.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qfiledialog.h>
+#include <qtooltip.h>
+
+#include <stdlib.h>
+
+#include "instrument.h"
+#include "stringinstrument.h"
+#include "pianoinstrument.h"
+#include "midiengine.h"
+
+
+
+
+class MainWin : public QWidget
+{
+Q_OBJECT
+
+       public:
+               MainWin();
+               ~MainWin();
+
+       public slots:
+               void interfaceSelectionSlot(int);
+               void setupRecord();
+               void finishRecord();
+
+       private:
+               // Functions
+               void instrumentLayouts();
+               void fillSounds();
+
+               // Widgets
+               Instrument *instrument;
+               QComboBox *soundSelection;
+               QComboBox *interfaceSelection;
+               QComboBox *octaveSelection;
+               QLabel *soundSelectionLabel;
+               QLabel *interfaceSelectionLabel;
+               QLabel *octaveSelectionLabel;
+               QLabel *aboutLabel;
+               QPushButton *helpBtn;
+               QPushButton *quitBtn;
+               QPushButton *recordBtn;
+               QPushButton *stopBtn;
+
+               // Layouts
+               QVBoxLayout *soundSelectionLayout;
+               QVBoxLayout *interfaceSelectionLayout;
+               QVBoxLayout *octaveSelectionLayout;
+               QVBoxLayout *midiBtnLayout;
+               QVBoxLayout *buttonsLayout;
+               QHBoxLayout *optionsLayout;
+               QVBoxLayout *vLayout;
+
+               MidiReal *midi;
+               MidiFile *midiFile;
+};
+
+
+
+#endif
diff --git a/midiengine.cpp b/midiengine.cpp
new file mode 100644 (file)
index 0000000..1ba3299
--- /dev/null
@@ -0,0 +1,653 @@
+// midiengine.cpp - Class to play to a sequencer or write to a MIDI file
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#include "midiengine.h"
+
+
+/* |---------------------------| */
+/* | Code for class MidiEngine | */
+/* |---------------------------| */
+
+
+bool MidiEngine::initSuccess()
+{
+       return !error;
+}
+
+QString MidiEngine::getError()
+{
+       return errorMessage;
+}
+
+bool MidiEngine::stopNote(int num)
+{
+       return playNote(num, 0, 0);
+}
+
+void MidiEngine::stopAll()
+{
+       // For all notes that have and ending, end them now
+       for(int i = 0; i <= 127; i++) {
+               if(noteEndings[i] != 0)
+                       stopNote(i);
+       }
+}
+
+void MidiEngine::timerEvent(QTimerEvent *)
+{
+       // Increment the timer
+       timer += 10;
+
+       // For all notes that expired after the current time, or on the current time:
+       // send a stop note
+       for(int i = 0; i <= 127; i++) {
+               if(noteEndings[i] <= timer && noteEndings[i] != 0) {
+                       playNote(i, 0, 0);
+               }
+       }
+}
+
+
+
+
+
+/* |-------------------------| */
+/* | Code for class MidiReal | */
+/* |-------------------------| */
+
+
+/* Linux code */
+
+#ifdef Q_WS_X11
+
+#include <linux/soundcard.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+
+class MidiReal::Private {
+       public:
+               // For setting up the midi output
+               int seqfd;
+               QString seqDevice;
+               int midiDevice;
+               unsigned char outpacket[12];
+};
+
+
+MidiReal::MidiReal()
+{
+       // Use to set the sequencer and device
+       QString dev = "/dev/sequencer";
+       int devicenum = 0;
+
+       d = new Private();
+
+       d->seqfd = -1;
+       d->seqDevice = dev;
+       d->midiDevice = devicenum;
+       error = true;
+       timer = 0;
+
+       // Open the sequencer file
+       d->seqfd = open(d->seqDevice.latin1(), O_WRONLY, 0);
+       if (d->seqfd < 0) {
+               errorMessage = "Cannot open sequencer: " + d->seqDevice;
+               return;
+       }
+
+       // Check if we can access the midi devices
+       int maxMidi = 0;
+       if(ioctl(d->seqfd, SNDCTL_SEQ_NRMIDIS, &maxMidi) != 0) {
+               errorMessage = "Cannot access MIDI devices on soundcard.";
+               return;
+       }
+       if(d->midiDevice >= maxMidi) {
+               errorMessage = "Invalid MIDI device. Valid devices are 0-" + QString::number(maxMidi - 1);
+               return;
+       }
+
+
+       // Good, no errors so far
+       error = false;
+
+
+       // Set all the note endings to zero
+       for(int i = 0; i <= 127; i++) {
+               noteEndings[i] = 0;
+       }
+
+       // Setup the outpacket
+       // The note on command
+       d->outpacket[0] = SEQ_MIDIPUTC;
+       d->outpacket[1] = 0x90;
+       d->outpacket[2] = d->midiDevice;
+       d->outpacket[3] = 0;
+
+       // Specify the note
+       d->outpacket[4] = SEQ_MIDIPUTC;
+       d->outpacket[5] = 0; // note number
+       d->outpacket[6] = d->midiDevice;
+       d->outpacket[7] = 0;
+
+       // Specify the volume
+       d->outpacket[8] = SEQ_MIDIPUTC;
+       d->outpacket[9] = 0; // volume
+       d->outpacket[10] = d->midiDevice;
+       d->outpacket[11] = 0;
+
+       // Start the timer so that we can end the notes
+       startTimer(10);
+}
+
+MidiReal::~MidiReal()
+{
+       // Stop all the notes
+       stopAll();
+
+       // Close the sequencer
+       if(d->seqfd > 0) {
+               close(d->seqfd);
+       }
+
+       delete d;
+}
+
+
+void MidiReal::setInstrument(int num)
+{
+       // Setup the MIDI packet
+       unsigned char outpacket[4];
+       outpacket[0] = SEQ_MIDIPUTC;
+       outpacket[2] = d->midiDevice;
+       outpacket[3] = 0;
+
+       // Write the "Change Patch" packet
+       outpacket[1] = 0xc0;
+       write(d->seqfd, outpacket, 4);
+
+       // Write the patch number packet
+       outpacket[1] = num & 0xff;
+       write(d->seqfd, outpacket, 4);
+}
+
+bool MidiReal::playNote(int num, int vel, int len)
+{
+       // Check the note and volume are in range
+       if(num > 127 || num < 0)
+               return false;
+
+       if(vel > 127 || vel < 0)
+               return false;
+
+       // Specify the note
+       d->outpacket[5] = num;
+
+       // Specify the volume
+       d->outpacket[9] = vel;
+
+       // Send it on it's way
+       write(d->seqfd, d->outpacket, 12);
+
+       // Set it up to turn off
+       if(len != 0 && vel != 0) {
+               noteEndings[num] = timer + len;
+       }
+
+//     if(vel != 0)
+//             printf("Playing note: %d\n", num);
+
+       return true;
+}
+
+#endif
+
+
+/* Windows code */
+
+#ifdef Q_WS_WIN
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+
+
+class MidiReal::Private {
+       public:
+               HMIDIOUT handle;
+
+               union {
+                       DWORD dwData;
+                       UCHAR bData[4];
+               } u;
+};
+
+MidiReal::MidiReal()
+{
+       d = new Private();
+
+       error = false;
+       timer = 0;
+
+       /* Open default MIDI Out device */
+       unsigned long err;
+       if(err = midiOutOpen(&(d->handle), (UINT)-1, 0, 0, CALLBACK_NULL)) {
+               error = true;
+               errorMessage = "Error opening MIDI: " + QString::number(err);
+               return;
+       }
+
+       // Set all the note endings to zero
+       for(int i = 0; i <= 127; i++) {
+               noteEndings[i] = 0;
+       }
+
+       // Start the timer so that we can end the notes
+       startTimer(10);
+}
+
+MidiReal::~MidiReal()
+{
+       // Stop all the notes
+       stopAll();
+
+       /* Close the MIDI device */
+       midiOutClose(d->handle);
+
+       delete d;
+}
+
+
+void MidiReal::setInstrument(int num)
+{
+       // Setup the MIDI packet
+       d->u.bData[0] = 0xc0;
+       d->u.bData[1] = num & 0xff;
+       d->u.bData[2] = 0;
+       d->u.bData[3] = 0;
+
+       // Write it
+       midiOutShortMsg(d->handle, d->u.dwData);
+}
+
+bool MidiReal::playNote(int num, int vel, int len)
+{
+       // Check the note and volume are in range
+       if(num > 127 || num < 0)
+               return false;
+
+       if(vel > 127 || vel < 0)
+               return false;
+
+       // Setup the MIDI packet
+       d->u.bData[0] = 0x90;
+       d->u.bData[1] = num;
+       d->u.bData[2] = vel;
+       d->u.bData[3] = 0;
+
+       // Write it
+       midiOutShortMsg(d->handle, d->u.dwData);
+
+       // Set it up to turn off
+       if(len != 0 && vel != 0) {
+               noteEndings[num] = timer + len;
+       }
+
+//     if(vel != 0)
+//             printf("Playing note: %d\n", num);
+
+       return true;
+}
+
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/* |-------------------------| */
+/* | Code for class MidiFile | */
+/* |-------------------------| */
+
+
+#include <stdio.h>
+
+#include <qvaluelist.h>
+
+
+
+
+class MidiFile::Private {
+public:
+       /* This information found at http://www.borg.com/~jglatt/tech/midifile.htm */
+
+       struct MThd_Chunk
+       {
+               /* Here's the 8 byte header that all chunks must have */
+               char           ID[4];  /* This will be 'M','T','h','d' */
+               unsigned long  Length; /* This will be 6 */
+
+               /* Here are the 6 bytes */
+               unsigned short Format;
+               unsigned short NumTracks;
+               unsigned short Division;
+       };
+
+       struct MTrk_Chunk
+       {
+               /* Here's the 8 byte header that all chunks must have */
+               char           ID[4];   /* This will be 'M','T','r','k' */
+               unsigned long  Length;  /* This will be the actual size of Data[] */
+
+               /* Here are the data bytes */
+               unsigned char *Data;    /* Its actual size is Data[Length] */
+       };
+
+       struct MTrk_Packet
+       {
+               unsigned long  deltaTime; /* The delta time - 4 bytes*/
+
+               /* The midi packet data */
+               unsigned char  midiData[7];
+
+               int lastGood;
+       };
+
+
+       /* A place to store the MTrk_Packets as we wait for them */
+       QValueList<struct MTrk_Packet> packets;
+
+       /* The time the last note was played, relative to MidiEngine::timer */
+       long int prevNoteTime;
+
+       /* Where we save the file to */
+       QString filename;
+       FILE *file;
+
+
+       /* Functions */
+       int twistVarLen(register unsigned long value, unsigned char str[4]); // 4 bytes
+       void convertLong2Array(unsigned long value, unsigned char str[4]);
+       void convertShort2Array(unsigned short value, unsigned char str[2]);
+       void writeBytes(unsigned char *str, int num);
+       void writeBytes(char *str, int num);
+       void writeMIDI();
+};
+
+int MidiFile::Private::twistVarLen(register unsigned long value, unsigned char str[4])
+{
+       register unsigned long buffer;
+       int i = 0;
+       buffer = value & 0x7F;
+
+       for(i = 0; i < 4; i++) str[i] = 0;
+
+       while ( (value >>= 7) )
+       {
+               buffer <<= 8;
+               buffer |= ((value & 0x7F) | 0x80);
+       }
+
+       for(i = 0;; i++)
+       {
+               str[i] = (unsigned char)buffer;
+               if (buffer & 0x80)
+                       buffer >>= 8;
+               else
+                       return i;
+       }
+}
+
+void MidiFile::Private::convertLong2Array(unsigned long value, unsigned char str[4])
+{
+       union {
+               char c[4];
+               unsigned long num;
+       } u;
+       u.num = value;
+       for(int i = 0, j = 3; i < 4; i++, j--)
+               str[i] = u.c[j];
+}
+
+void MidiFile::Private::convertShort2Array(unsigned short value, unsigned char str[2])
+{
+       union {
+               char c[2];
+               unsigned short num;
+       } u;
+       u.num = value;
+       str[1] = u.c[0];
+       str[0] = u.c[1];
+}
+
+void MidiFile::Private::writeBytes(unsigned char *str, int num)
+{
+       for(int i = 0; i < num; i++)
+               fputc(str[i], file);
+}
+
+void MidiFile::Private::writeBytes(char *str, int num)
+{
+       for(int i = 0; i < num; i++)
+               fputc(str[i], file);
+}
+
+void MidiFile::Private::writeMIDI()
+{
+       if(file == 0)
+               return;
+
+       unsigned char str[4];
+       int i = 0;
+       int j = 0;
+
+       // Create the MThd header
+       struct MThd_Chunk h;
+       h.ID[0] = 'M';
+       h.ID[1] = 'T';
+       h.ID[2] = 'h';
+       h.ID[3] = 'd';
+       h.Length = 6;
+       h.Format = 0;
+       h.NumTracks = 1;
+       h.Division = 500;
+
+       // Create the MTrk chunk and allocate space for the MIDI packets
+       struct MTrk_Chunk t;
+       t.ID[0] = 'M';
+       t.ID[1] = 'T';
+       t.ID[2] = 'r';
+       t.ID[3] = 'k';
+       t.Length = packets.count() * sizeof(struct MTrk_Packet); // Only an approximation
+       t.Data = new unsigned char[t.Length];
+
+
+       // Store the MIDI packets in the MTrk chunk
+       QValueList<struct MTrk_Packet>::Iterator it;
+       int pos = 0;
+       for(it = packets.begin(); it != packets.end(); ++it) {
+               // Twist the deltaTime, then copy it
+               j = twistVarLen((*it).deltaTime, str);
+               // Copy now..
+               for(i = 0; i <= j; i++) t.Data[pos + i] = str[i];
+
+               pos = pos + i; // New position
+
+
+               // Copy the MIDI bytes also
+               for(i = 0; i <= (*it).lastGood; i++)
+                       t.Data[pos + i] = (*it).midiData[i];
+
+               pos = pos + i;
+       }
+       t.Length = pos;
+
+
+
+       /* Write all the MIDI packets to disk */
+
+       // Writing the header
+       // Write h.ID
+       writeBytes(h.ID, 4);
+       // Write h.Length
+       convertLong2Array(h.Length, str);
+       writeBytes(str, 4);
+       // Write the format
+       convertShort2Array(h.Format, str);
+       writeBytes(str, 2);
+       // Write the NumTracks
+       convertShort2Array(h.NumTracks, str);
+       writeBytes(str, 2);
+       // Hack hack, write the Division
+       convertShort2Array(h.Division, str);
+       writeBytes(str, 2);
+//     fputc(-25, file);
+//     fputc(40, file);
+
+
+       // Now writing the track
+       // Write t.ID
+       writeBytes(t.ID, 4);
+       // Write the length
+       convertLong2Array(t.Length, str);
+       writeBytes(str, 4);
+       // Copy the track data
+       writeBytes(t.Data, t.Length);
+
+
+       // Free it again
+       delete [] t.Data;
+}
+
+
+MidiFile::MidiFile(QString file)
+{
+       d = new Private();
+
+       d->filename = file;
+
+       error = false;
+       timer = 0;
+       d->prevNoteTime = -1;
+
+       // Open the MIDI file to send output to
+       d->file = fopen(d->filename.latin1(), "wb");
+       if (d->file == 0) {
+               errorMessage = "Cannot open output MIDI file: " + d->filename;
+               return;
+       }
+
+       // Set all the note endings to zero
+       for(int i = 0; i <= 127; i++) {
+               noteEndings[i] = 0;
+       }
+
+       // Start the timer so that we can end the notes
+       startTimer(10);
+}
+
+MidiFile::~MidiFile()
+{
+       // Stop all the notes
+       stopAll();
+
+       // End track note
+       struct Private::MTrk_Packet pak;
+       pak.deltaTime = 0;
+       pak.midiData[0] = 0xff;
+       pak.midiData[1] = 0x2f;
+       pak.midiData[2] = 0x00;
+       pak.lastGood = 2;
+       d->packets.append(pak);
+
+       // Write the MIDI
+       d->writeMIDI();
+
+       // Close the MIDI file
+       if(d->file != 0) {
+               fclose(d->file);
+       }
+
+       delete d;
+}
+
+
+void MidiFile::setInstrument(int num)
+{
+       // Setup the MIDI packet
+       struct Private::MTrk_Packet pak;
+
+       // Set the deltaTime
+       if(d->prevNoteTime == -1) // If we're the first note, start from 0, but don't let us count (only a patch change)
+               pak.deltaTime = 0;
+       else
+               d->prevNoteTime = timer;
+
+       pak.midiData[0] = 0xc0;
+       pak.midiData[1] = num & 0xff;
+       pak.lastGood = 1; // Last byte with data
+
+       d->packets.append(pak);
+}
+
+bool MidiFile::playNote(int num, int vel, int len)
+{
+       // Check the note and volume are in range
+       if(num > 127 || num < 0)
+               return false;
+
+       if(vel > 127 || vel < 0)
+               return false;
+
+
+       // Setup the MIDI packet
+       struct Private::MTrk_Packet pak;
+
+       // Set the deltaTime
+       if(d->prevNoteTime == -1 && vel != 0) // If we're the first note, start from 0
+               d->prevNoteTime = timer;
+       pak.deltaTime = timer - d->prevNoteTime;
+       d->prevNoteTime = timer;
+
+       if(vel > 0) {
+               // Setup the MIDI packet
+               pak.midiData[0] = 0x90;
+               pak.midiData[1] = num;
+               pak.midiData[2] = vel;
+               pak.lastGood = 2; // Last byte with good data
+       }
+       else {
+               // Setup the MIDI packet
+               pak.midiData[0] = 0x80;
+               pak.midiData[1] = num;
+               pak.midiData[2] = 64; // How quickly the note goes away
+               pak.lastGood = 2; // Last byte with good data
+       }
+
+       // Set it up to turn off
+       if(len != 0 && vel != 0) {
+               noteEndings[num] = timer + len;
+       }
+
+//     if(vel != 0)
+//             printf("Appending note: %d with deltaTime: %d\n", num, pak.deltaTime);
+
+       d->packets.append(pak);
+
+       return true;
+}
+
+
diff --git a/midiengine.h b/midiengine.h
new file mode 100644 (file)
index 0000000..ea5931e
--- /dev/null
@@ -0,0 +1,101 @@
+// midiengine.h - Class to play to a sequencer or write to a MIDI file
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#ifndef MIDIENGINE_H
+#define MIDIENGINE_H
+
+
+#include <qobject.h>
+#include <qevent.h>
+#include <qstring.h>
+
+
+class MidiEngine : public QObject
+{
+Q_OBJECT
+       public:
+               /* initSuccess() - returns true if the object is ready to be used */
+               bool initSuccess();
+
+               /* getError() - returns an error message describing why the object isn't ready */
+               QString getError();
+
+
+       public slots:
+               /* setPatch() - Allows you to set the instrument
+                       'num' - the number of the patchset you wish to use
+                       Note: The patchset had better exist!
+               */
+               virtual void setInstrument(int num)=0;
+
+
+               /* playNote() - Plays a midi note
+                       'num' - the note to play (between 0 and 127. 69 in middle C)
+                       'vel' - the note volume (between 0 and 127), if 0 then the note is stopped
+                       'len' - the duration in milliseconds, if 0, then note will play until stopped
+                       returns false if it received incorrect parameters
+               */
+               virtual bool playNote(int num, int vel, int len)=0;
+
+
+               /* stopNote() - Stops a midi note
+                       'num' - the note to stop
+               */
+               bool stopNote(int num);
+
+
+               /* stopAll() - Stops all currently playing MIDI notes */
+               void stopAll();
+
+       protected:
+               virtual void timerEvent(QTimerEvent *);
+
+               // For dealing with errors
+               QString errorMessage;
+               bool error;
+
+               // For turning notes off
+               long int noteEndings[128];
+               long int timer; // In milliseconds, only approximate
+};
+
+
+class MidiReal : public MidiEngine
+{
+Q_OBJECT
+       public:
+               MidiReal();
+               ~MidiReal();
+
+       public slots:
+               void setInstrument(int num);
+               bool playNote(int num, int vel, int len);
+
+       private:
+               class Private;
+               Private *d;
+};
+
+
+
+class MidiFile : public MidiEngine {
+Q_OBJECT
+       public:
+               MidiFile(QString outFile);
+               ~MidiFile();
+
+       public slots:
+               void setInstrument(int num);
+               bool playNote(int num, int vel, int len);
+
+       private:
+               class Private;
+               Private *d;
+};
+
+
+
+#endif
+
diff --git a/piano.png b/piano.png
new file mode 100644 (file)
index 0000000..72da3d4
Binary files /dev/null and b/piano.png differ
diff --git a/pianoinstrument.cpp b/pianoinstrument.cpp
new file mode 100644 (file)
index 0000000..67ebc13
--- /dev/null
@@ -0,0 +1,387 @@
+// pianoinstrument.cpp - A piano simulator
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#include "pianoinstrument.h"
+
+
+
+
+PianoInstrument::PianoInstrument(QWidget *parent)
+: Instrument(parent)
+{
+       // Set us up to look pretty
+       setPaletteBackgroundPixmap(QPixmap("piano.png"));
+       setFixedSize(184, 220);
+       parentWidget()->setFixedSize(184, 220);
+
+       for(int i = 0; i < 26; i++) {
+               oldNotes[i] = false;
+               notes[i] = false;
+       }
+
+       noteStart = 48;
+
+       emitSounds();
+}
+
+PianoInstrument::~PianoInstrument()
+{
+
+}
+
+QString PianoInstrument::generateHelp()
+{
+       QString help;
+       help +=
+
+"<html>"
+
+"Playing the keyboard:"
+"<ul>"
+"<li>You can change the octave using the &quot;Octave&quot; box above. Middle C is the third octave</li>"
+"<li>The keys - qwertyui are the top row white keys</li>"
+"<li>The keys - 23 567 9 are the top row black keys</li>"
+"<li>The keys - zxcvbnm, are the bottom row white keys</li>"
+"<li>The keys - sd ghj l are the bottom row black keys</li>"
+"</ul>"
+
+"</html>"
+;
+       return help;
+}
+
+
+void PianoInstrument::paintEvent(QPaintEvent *)
+{
+       QPainter paint(this);
+       paint.setPen(Qt::red);
+
+       const int topBlackY = 38;
+       const int topWhiteY = 70;
+       const int botBlackY = 38 + 110;
+       const int botWhiteY = 70 + 110;
+       const int w = 10;
+       const int h = 10;
+
+       if(notes[0] == true) {
+               paint.drawEllipse(6, topWhiteY, w, h);
+       }
+       if(notes[1] == true) {
+               paint.drawEllipse(15, topBlackY, w, h);
+       }
+       if(notes[2] == true) {
+               paint.drawEllipse(29, topWhiteY, w, h);
+       }
+       if(notes[3] == true) {
+               paint.drawEllipse(45, topBlackY, w, h);
+       }
+       if(notes[4] == true) {
+               paint.drawEllipse(52, topWhiteY, w, h);
+       }
+       if(notes[5] == true) {
+               paint.drawEllipse(75, topWhiteY, w, h);
+       }
+       if(notes[6] == true) {
+               paint.drawEllipse(85, topBlackY, w, h);
+       }
+       if(notes[7] == true) {
+               paint.drawEllipse(97, topWhiteY, w, h);
+       }
+       if(notes[8] == true) {
+               paint.drawEllipse(113, topBlackY, w, h);
+       }
+       if(notes[9] == true) {
+               paint.drawEllipse(120, topWhiteY, w, h);
+       }
+       if(notes[10] == true) {
+               paint.drawEllipse(136, topBlackY, w, h);
+       }
+       if(notes[11] == true) {
+               paint.drawEllipse(143, topWhiteY, w, h);
+       }
+       if(notes[12] == true) {
+               paint.drawEllipse(166, topWhiteY, w, h);
+               paint.drawEllipse(6, botWhiteY, w, h);
+       }
+       if(notes[13] == true) {
+               paint.drawEllipse(176, topBlackY, w, h);
+               paint.drawEllipse(15, botBlackY, w, h);
+       }
+       if(notes[14] == true) {
+               paint.drawEllipse(29, botWhiteY, w, h);
+       }
+       if(notes[15] == true) {
+               paint.drawEllipse(45, botBlackY, w, h);
+       }
+       if(notes[16] == true) {
+               paint.drawEllipse(52, botWhiteY, w, h);
+       }
+       if(notes[17] == true) {
+               paint.drawEllipse(75, botWhiteY, w, h);
+       }
+       if(notes[18] == true) {
+               paint.drawEllipse(85, botBlackY, w, h);
+       }
+       if(notes[19] == true) {
+               paint.drawEllipse(97, botWhiteY, w, h);
+       }
+       if(notes[20] == true) {
+               paint.drawEllipse(113, botBlackY, w, h);
+       }
+       if(notes[21] == true) {
+               paint.drawEllipse(120, botWhiteY, w, h);
+       }
+       if(notes[22] == true) {
+               paint.drawEllipse(136, botBlackY, w, h);
+       }
+       if(notes[23] == true) {
+               paint.drawEllipse(143, botWhiteY, w, h);
+       }
+       if(notes[24] == true) {
+               paint.drawEllipse(166, botWhiteY, w, h);
+       }
+       if(notes[25] == true) {
+               paint.drawEllipse(176, botBlackY, w, h);
+       }
+
+}
+
+
+void PianoInstrument::keyPressEvent(QKeyEvent *e)
+{
+       if(e->isAutoRepeat() == true) {
+               e->ignore();
+               return;
+       }
+
+       // Make a copy of the old notes so we know what's changed
+       copyArray(notes, oldNotes);
+
+       switch(e->key()) {
+
+       /* First row of keys */
+       case Key_Q:
+               notes[0] = true;
+               break;
+       case Key_2:
+               notes[1] = true;
+               break;
+       case Key_W:
+               notes[2] = true;
+               break;
+       case Key_3:
+               notes[3] = true;
+               break;
+       case Key_E:
+               notes[4] = true;
+               break;
+       case Key_R:
+               notes[5] = true;
+               break;
+       case Key_5:
+               notes[6] = true;
+               break;
+       case Key_T:
+               notes[7] = true;
+               break;
+       case Key_6:
+               notes[8] = true;
+               break;
+       case Key_Y:
+               notes[9] = true;
+               break;
+       case Key_7:
+               notes[10] = true;
+               break;
+       case Key_U:
+               notes[11] = true;
+               break;
+       case Key_I:
+               notes[12] = true;
+               break;
+       case Key_9:
+               notes[13] = true;
+               break;
+
+       /* Second row of keys */
+       case Key_Z:
+               notes[12] = true;
+               break;
+       case Key_S:
+               notes[13] = true;
+               break;
+       case Key_X:
+               notes[14] = true;
+               break;
+       case Key_D:
+               notes[15] = true;
+               break;
+       case Key_C:
+               notes[16] = true;
+               break;
+       case Key_V:
+               notes[17] = true;
+               break;
+       case Key_G:
+               notes[18] = true;
+               break;
+       case Key_B:
+               notes[19] = true;
+               break;
+       case Key_H:
+               notes[20] = true;
+               break;
+       case Key_N:
+               notes[21] = true;
+               break;
+       case Key_J:
+               notes[22] = true;
+               break;
+       case Key_M:
+               notes[23] = true;
+               break;
+       case Key_Comma:
+               notes[24] = true;
+               break;
+       case Key_L:
+               notes[25] = true;
+               break;
+       default:
+               e->ignore();
+               return;
+       }
+       e->accept();
+       emitSounds();
+}
+
+void PianoInstrument::keyReleaseEvent(QKeyEvent *e)
+{
+       if(e->isAutoRepeat() == true) {
+               e->ignore();
+               return;
+       }
+
+       // Make a copy of the old notes so we know what's changed
+       copyArray(notes, oldNotes);
+
+       switch(e->key()) {
+
+       /* First row of keys */
+       case Key_Q:
+               notes[0] = false;
+               break;
+       case Key_2:
+               notes[1] = false;
+               break;
+       case Key_W:
+               notes[2] = false;
+               break;
+       case Key_3:
+               notes[3] = false;
+               break;
+       case Key_E:
+               notes[4] = false;
+               break;
+       case Key_R:
+               notes[5] = false;
+               break;
+       case Key_5:
+               notes[6] = false;
+               break;
+       case Key_T:
+               notes[7] = false;
+               break;
+       case Key_6:
+               notes[8] = false;
+               break;
+       case Key_Y:
+               notes[9] = false;
+               break;
+       case Key_7:
+               notes[10] = false;
+               break;
+       case Key_U:
+               notes[11] = false;
+               break;
+       case Key_I:
+               notes[12] = false;
+               break;
+       case Key_9:
+               notes[13] = false;
+               break;
+
+       /* Second row of keys */
+       case Key_Z:
+               notes[12] = false;
+               break;
+       case Key_S:
+               notes[13] = false;
+               break;
+       case Key_X:
+               notes[14] = false;
+               break;
+       case Key_D:
+               notes[15] = false;
+               break;
+       case Key_C:
+               notes[16] = false;
+               break;
+       case Key_V:
+               notes[17] = false;
+               break;
+       case Key_G:
+               notes[18] = false;
+               break;
+       case Key_B:
+               notes[19] = false;
+               break;
+       case Key_H:
+               notes[20] = false;
+               break;
+       case Key_N:
+               notes[21] = false;
+               break;
+       case Key_J:
+               notes[22] = false;
+               break;
+       case Key_M:
+               notes[23] = false;
+               break;
+       case Key_Comma:
+               notes[24] = false;
+               break;
+       case Key_L:
+               notes[25] = false;
+               break;
+       default:
+               e->ignore();
+               return;
+       }
+       e->accept();
+       emitSounds();
+}
+
+void PianoInstrument::copyArray(bool source[26], bool dest[26])
+{
+       for(int i = 0; i < 26; i++) {
+               dest[i] = source[i];
+       }
+}
+
+void PianoInstrument::emitSounds()
+{
+       for(int i = 0; i < 26; i++) {
+               if(notes[i] == oldNotes[i]) continue;
+
+               if(notes[i] == false)
+                       emit stopNote(i + noteStart);
+               else {
+                       emit playNote(i + noteStart, 120, 0);
+               }
+       }
+
+       repaint();
+}
+
diff --git a/pianoinstrument.h b/pianoinstrument.h
new file mode 100644 (file)
index 0000000..53f823b
--- /dev/null
@@ -0,0 +1,40 @@
+// pianoinstrument.h - A piano simulator
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#ifndef PIANOINSTRUMENT_H
+#define PIANOINSTRUMENT_H
+
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+
+#include "instrument.h"
+
+
+class PianoInstrument : public Instrument
+{
+Q_OBJECT
+       public:
+               PianoInstrument(QWidget *parent);
+               ~PianoInstrument();
+
+       protected:
+               QString generateHelp();
+               void paintEvent(QPaintEvent *);
+               void keyPressEvent(QKeyEvent *);
+               void keyReleaseEvent(QKeyEvent *);
+
+       private:
+               void paintPart(QPainter &paint, int start, int stop, int y, bool sharp);
+               void copyArray(bool source[26], bool dest[26]);
+               void emitSounds();
+
+               bool oldNotes[26];
+               bool notes[26];
+};
+
+
+
+#endif
diff --git a/stringinstrument.cpp b/stringinstrument.cpp
new file mode 100644 (file)
index 0000000..b9ba392
--- /dev/null
@@ -0,0 +1,572 @@
+// stringinstrument.cpp - A stringed instrument simulator
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#include "stringinstrument.h"
+
+// Images from http://www.asinari.it/bassoeng.htm
+
+ViolinInstrument::ViolinInstrument(QWidget *parent)
+: StringInstrument(parent)
+{
+       noteStart = 48;
+
+       // Set us up to look pretty
+       setPaletteBackgroundPixmap(QPixmap("violin.png"));
+       setFixedSize(637, 384);
+       parentWidget()->setFixedSize(637,384);
+
+       setNotes(stringnote);
+}
+
+void ViolinInstrument::setNotes(int array[4])
+{
+       array[0] = 28; // 'E' string
+       array[1] = 21; // 'A' string
+       array[2] = 14; // 'D' string
+       array[3] = 7;  // 'G' string
+}
+
+
+
+ViolaInstrument::ViolaInstrument(QWidget *parent)
+: StringInstrument(parent)
+{
+       noteStart = 48;
+
+       // Set us up to look pretty
+       setPaletteBackgroundPixmap(QPixmap("viola.png"));
+       setFixedSize(638, 392);
+       parentWidget()->setFixedSize(638,392);
+
+       setNotes(stringnote);
+}
+
+void ViolaInstrument::setNotes(int array[4])
+{
+       array[0] = 21; // 'A' string below middle c
+       array[1] = 14; // 'D' string
+       array[2] = 7; // 'G' string
+       array[3] = 0; // 'C' string
+}
+
+
+
+CelloInstrument::CelloInstrument(QWidget *parent)
+: StringInstrument(parent)
+{
+       noteStart = 36;
+
+       // Set us up to look pretty
+       setPaletteBackgroundPixmap(QPixmap("cello.png"));
+       setFixedSize(639, 391);
+       parentWidget()->setFixedSize(639,391);
+
+       setNotes(stringnote);
+}
+
+void CelloInstrument::setNotes(int array[4])
+{
+       array[0] = 21; // 'A' string 2 below middle c
+       array[1] = 14; // 'D' string
+       array[2] = 7; // 'G' string
+       array[3] = 0; // 'C' string
+}
+
+
+
+ContrabassInstrument::ContrabassInstrument(QWidget *parent)
+: StringInstrument(parent)
+{
+       noteStart = 24;
+
+       // Set us up to look pretty
+       setPaletteBackgroundPixmap(QPixmap("contrabass.png"));
+       setFixedSize(638, 388);
+       parentWidget()->setFixedSize(637,384);
+
+       setNotes(stringnote);
+}
+
+void ContrabassInstrument::setNotes(int array[4])
+{
+       array[0] = 19; // 'G' string 2 below middle c
+       array[1] = 14; // 'D' string
+       array[2] = 9; // 'A' string
+       array[3] = 4; // 'E' string
+}
+
+
+
+/* StringInstrument */
+
+/* For reference (Violin).. First line, note, second line key presses, third line, MIDI number
+String E:
+       E       F       F#      G       G#      A       A#      B
+       None    1       12      2       23      3       34      4
+       76      77      78      79      80      81      82      83
+
+String A:
+       A       A#      B       C       C#      D       D#      E
+       None    TabQ    Q       W       WE      E       ER      R
+       69      70      71      72      73      74      75      76
+
+String D:
+       D       D#      E       F       F#      G       G#      A
+       None    CapsA   A       S       SD      D       DF      F
+       62      63      64      65      66      67      68      69
+
+String G:
+       G       G#      A       A#      B       C       C#      D
+       None    ShiftZ  Z       ZX      X       C       CV      V
+       55      56      57      58      59      60      61      62
+*/
+
+
+StringInstrument::StringInstrument(QWidget *parent)
+: Instrument(parent)
+{
+       // Set all the keys to false
+       zeroArray(down);
+       zeroArray(downFudge);
+       zeroArray(bow);
+       zeroArray(oldVolume);
+       zeroArray(oldNote);
+       zeroArray(note);
+       zeroArray(volume);
+
+       emitSounds();
+}
+
+StringInstrument::~StringInstrument()
+{
+}
+
+QString StringInstrument::generateHelp()
+{
+       QString help;
+       help +=
+
+"<html>"
+
+"Setting notes:"
+"<ul>"
+"<li>The row '1' '2' '3' '4' is the E string</li>"
+"<li>The row 'q' 'w' 'e' 'r' is the A string</li>"
+"<li>The row 'a' 's' 'd' 'f' is the D string</li>"
+"<li>The row 'z' 'x' 'c' 'v' is the G string</li>"
+"<li>The further right the key is, the higher the note</li>"
+"<li>Try pressing multiple keys on the same string to get sharp notes. On the A,D,G strings try the Tab, Caps and Shift keys for sharp notes</li>"
+"</ul>"
+
+"Bowing:"
+"<ul>"
+"<li>Use the keys '7' '8' '9' '0' to bow the E string</li>"
+"<li>Use the keys 'u' 'i' 'o' 'p' to bow the A string</li>"
+"<li>Use the keys 'j' 'k' 'l' ';' to bow the D string</li>"
+"<li>Use the keys 'm' ',' '.' '/' to bow the G string</li>"
+"<li>The further right the key is, the quieter the note</li>"
+"</ul>"
+
+"</html>"
+;
+       return help;
+}
+
+void StringInstrument::paintEvent(QPaintEvent *) {
+       QPainter paint(this);
+       paint.setPen(Qt::black);
+
+       for(int i = 0; i < 4; i++) {
+               QString text;
+               text += "Playing note: ";
+               text += midi2string(note[i]);
+               text += " - Volume: " + QString::number(volume[i] / 30);
+               paint.drawText(45, (i+1)*20, text);
+       }
+}
+
+void StringInstrument::keyPressEvent(QKeyEvent *e)
+{
+       if(e->isAutoRepeat() == true) {
+               e->ignore();
+               return;
+       }
+
+       switch(e->key()) {
+       /* Fudge keys */
+//     case Key_Tilde: // This is never used
+//             downFudge[0] = true;
+//             break;
+       case Key_Backtab:
+       case Key_Tab:
+               downFudge[1] = true;
+               break;
+       case Key_CapsLock:
+               downFudge[2] = true;
+               break;
+       case Key_Shift:
+               downFudge[3] = true;
+               break;
+
+       /* First string */
+       case Key_1:
+               down[0][0] = true;
+               break;
+       case Key_2:
+               down[0][1] = true;
+               break;
+       case Key_3:
+               down[0][2] = true;
+               break;
+       case Key_4:
+               down[0][3] = true;
+               break;
+
+       /* Second string */
+       case Key_Q:
+               down[1][0] = true;
+               break;
+       case Key_W:
+               down[1][1] = true;
+               break;
+       case Key_E:
+               down[1][2] = true;
+               break;
+       case Key_R:
+               down[1][3] = true;
+               break;
+
+       /* Third string */
+       case Key_A:
+               down[2][0] = true;
+               break;
+       case Key_S:
+               down[2][1] = true;
+               break;
+       case Key_D:
+               down[2][2] = true;
+               break;
+       case Key_F:
+               down[2][3] = true;
+               break;
+
+       /* Fourth string */
+       case Key_Z:
+               down[3][0] = true;
+               break;
+       case Key_X:
+               down[3][1] = true;
+               break;
+       case Key_C:
+               down[3][2] = true;
+               break;
+       case Key_V:
+               down[3][3] = true;
+               break;
+
+       // Bowing on volume 3 (max)
+       case Key_7:
+               bow[0][3] = true;
+               break;
+       case Key_U:
+               bow[1][3] = true;
+               break;
+       case Key_J:
+               bow[2][3] = true;
+               break;
+       case Key_M:
+               bow[3][3] = true;
+               break;
+
+       // Bowing on volume 2
+       case Key_8:
+               bow[0][2] = true;
+               break;
+       case Key_I:
+               bow[1][2] = true;
+               break;
+       case Key_K:
+               bow[2][2] = true;
+               break;
+       case Key_Comma:
+               bow[3][2] = true;
+               break;
+
+       // Bowing on volume 1
+       case Key_9:
+               bow[0][1] = true;
+               break;
+       case Key_O:
+               bow[1][1] = true;
+               break;
+       case Key_L:
+               bow[2][1] = true;
+               break;
+       case Key_Period:
+               bow[3][1] = true;
+               break;
+
+       // Bowing on volume 0 (min)
+       case Key_0:
+               bow[0][0] = true;
+               break;
+       case Key_P:
+               bow[1][0] = true;
+               break;
+       case Key_Semicolon:
+               bow[2][0] = true;
+               break;
+       case Key_Slash:
+               bow[3][0] = true;
+               break;
+
+       case Key_F12:
+               zeroArray(down);
+               zeroArray(bow);
+                break;
+
+       default:
+               e->ignore();
+               return;
+       }
+       e->accept();
+       emitSounds();
+}
+
+void StringInstrument::keyReleaseEvent(QKeyEvent *e)
+{
+       if(e->isAutoRepeat() == true) {
+               e->ignore();
+               return;
+       }
+
+       switch(e->key()) {
+       /* Fudge keys */
+//     case Key_Tilde: // This is never used
+//             downFudge[0] = true;
+//             break;
+       case Key_Backtab:
+       case Key_Tab:
+               downFudge[1] = false;
+               break;
+       case Key_CapsLock:
+               downFudge[2] = false;
+               break;
+       case Key_Shift:
+               downFudge[3] = false;
+               break;
+
+       /* First string */
+       case Key_1:
+               down[0][0] = false;
+               break;
+       case Key_2:
+               down[0][1] = false;
+               break;
+       case Key_3:
+               down[0][2] = false;
+               break;
+       case Key_4:
+               down[0][3] = false;
+               break;
+
+       /* Second string */
+       case Key_Q:
+               down[1][0] = false;
+               break;
+       case Key_W:
+               down[1][1] = false;
+               break;
+       case Key_E:
+               down[1][2] = false;
+               break;
+       case Key_R:
+               down[1][3] = false;
+               break;
+
+       /* Third string */
+       case Key_A:
+               down[2][0] = false;
+               break;
+       case Key_S:
+               down[2][1] = false;
+               break;
+       case Key_D:
+               down[2][2] = false;
+               break;
+       case Key_F:
+               down[2][3] = false;
+               break;
+
+       /* Fourth string */
+       case Key_Z:
+               down[3][0] = false;
+               break;
+       case Key_X:
+               down[3][1] = false;
+               break;
+       case Key_C:
+               down[3][2] = false;
+               break;
+       case Key_V:
+               down[3][3] = false;
+               break;
+
+       // Bowing on volume 3 (max)
+       case Key_7:
+               bow[0][3] = false;
+               break;
+       case Key_U:
+               bow[1][3] = false;
+               break;
+       case Key_J:
+               bow[2][3] = false;
+               break;
+       case Key_M:
+               bow[3][3] = false;
+               break;
+
+       // Bowing on volume 2
+       case Key_8:
+               bow[0][2] = false;
+               break;
+       case Key_I:
+               bow[1][2] = false;
+               break;
+       case Key_K:
+               bow[2][2] = false;
+               break;
+       case Key_Comma:
+               bow[3][2] = false;
+               break;
+
+       // Bowing on volume 1
+       case Key_9:
+               bow[0][1] = false;
+               break;
+       case Key_O:
+               bow[1][1] = false;
+               break;
+       case Key_L:
+               bow[2][1] = false;
+               break;
+       case Key_Period:
+               bow[3][1] = false;
+               break;
+
+       // Bowing on volume 0 (min)
+       case Key_0:
+               bow[0][0] = false;
+               break;
+       case Key_P:
+               bow[1][0] = false;
+               break;
+       case Key_Semicolon:
+               bow[2][0] = false;
+               break;
+       case Key_Slash:
+               bow[3][0] = false;
+               break;
+
+       case Key_F12:
+               zeroArray(down);
+               zeroArray(bow);
+                break;
+
+       default:
+               e->ignore();
+               return;
+       }
+       e->accept();
+       emitSounds();
+}
+
+void StringInstrument::zeroArray(bool array[4][4])
+{
+       for(int i = 0; i < 4; i++) {
+               for(int j = 0; j < 4; j++) {
+                       array[i][j] = false;
+               }
+       }
+}
+
+void StringInstrument::zeroArray(bool array[4])
+{
+       for(int i = 0; i < 4; i++) {
+               array[i] = false;
+       }
+
+}
+
+void StringInstrument::zeroArray(int array[4])
+{
+       for(int i = 0; i < 4; i++) {
+               array[i] = 0;
+       }
+}
+
+void StringInstrument::copyArray(int source[4], int dest[4])
+{
+       for(int i = 0; i < 4; i++) {
+               dest[i] = source[i];
+       }
+}
+
+void StringInstrument::emitSounds()
+{
+       copyArray(volume, oldVolume);
+       copyArray(note, oldNote);
+
+       zeroArray(volume);
+       copyArray(stringnote, note); // Fudge for setNotes(note);
+
+       // Need to find the differences in oldVolume, volume and oldNote, note
+       // Then stop changed notes, and start new ones
+
+
+       // Get the volumes, 'i' is the string, 'j' is the volume
+       int i;
+       for(i = 0; i < 4; i++) {
+               for(int j = 0; j < 4; j++) {
+                       if(bow[i][j] == true) {
+                               volume[i] = (j + 1) * 30;
+                       }
+               }
+               if(volume[i] != oldVolume[i]) { // The volume changed, stop that string
+                       emit stopNote(oldNote[i] + noteStart);
+                       oldNote[i] = -1; // Flag to restart, just in case the pitch didn't change
+               }
+       }
+
+       // Get the notes, 'i' is the string, 'j' is the volume
+       for(i = 0; i < 4; i++) {
+               for(int j = 0; j < 4; j++) {
+                       if(down[i][j] == false) {
+                               continue;
+                       }
+                       note[i] = stringnote[i] + 2 * (j + 1);
+                       for(int k = stringnote[i]; k <= note[i]; k++) {
+                               if(checkSharp(k) == true) {
+                                       note[i] -= 1;
+                               }
+                       }
+                       if(j > 0 && down[i][j] == true && down[i][j-1] == true) {
+                               note[i] -= 1;
+                       }
+                       else if(j == 0 && downFudge[i] == true) {
+                               note[i] -= 1;
+                       }
+               }
+               if(note[i] != oldNote[i]) { // The pitch changed, restart the string
+                       // Stop the old one
+                       if(oldNote[i] != -1) {
+                               emit stopNote(oldNote[i] + noteStart);
+                       }
+                       // Start the new
+                       emit playNote(note[i] + noteStart, volume[i], 0);
+               }
+       }
+       repaint();
+}
diff --git a/stringinstrument.h b/stringinstrument.h
new file mode 100644 (file)
index 0000000..e88d29f
--- /dev/null
@@ -0,0 +1,103 @@
+// stringinstrument.h - A stringed instrument simulator
+// Written by James Bunton <james@delx.cjb.net>
+// Licensed under the GPL, see COPYING.txt for more details
+
+
+#ifndef STRINGINSTRUMENT_H
+#define STRINGINSTRUMENT_H
+
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+
+#include "instrument.h"
+
+
+
+class StringInstrument : public Instrument
+{
+Q_OBJECT
+       public:
+               StringInstrument(QWidget *parent);
+               ~StringInstrument();
+
+       protected:
+               QString generateHelp();
+               void paintEvent(QPaintEvent *);
+               void keyPressEvent(QKeyEvent *);
+               void keyReleaseEvent(QKeyEvent *);
+
+
+               void zeroArray(bool array[4][4]);
+               void zeroArray(bool array[4]);
+               void zeroArray(int array[4]);
+               virtual void setNotes(int array[4])=0; // Set the base string notes
+               void copyArray(int source[4], int dest[4]);
+               void emitSounds();
+
+               // Keys
+               bool down[4][4];
+               // down[1][3] == true, means that when the D string is bowed, G will be played
+               // First dimension is the string, second is the modifier
+               bool downFudge[4];
+               // Fudge keys. They're at the beginning of each string and do not play a note by themself
+               // downFudge[0] is '~', [1] is tab, etc.. They allow access to sharps
+
+               bool bow[4][4];
+               // bow[1][4] == true means that the D string will be bowed at the highest volume
+               // First dimension is the string to be bowed, second is the volume
+
+               int stringnote[4];
+               // Base notes for each string
+
+
+               // The volumes and notes to play for each string
+               int oldVolume[4];
+               int oldNote[4];
+               int volume[4];
+               int note[4];
+};
+
+
+class ViolinInstrument : public StringInstrument
+{
+public:
+       ViolinInstrument(QWidget *parent);
+       ~ViolinInstrument() {};
+protected:
+       void setNotes(int array[4]);
+};
+
+
+class ViolaInstrument : public StringInstrument
+{
+public:
+       ViolaInstrument(QWidget *parent);
+       ~ViolaInstrument() {};
+protected:
+       void setNotes(int array[4]);
+};
+
+
+class CelloInstrument : public StringInstrument
+{
+public:
+       CelloInstrument(QWidget *parent);
+       ~CelloInstrument() {};
+protected:
+       void setNotes(int array[4]);
+};
+
+
+class ContrabassInstrument : public StringInstrument
+{
+public:
+       ContrabassInstrument(QWidget *parent);
+       ~ContrabassInstrument() {};
+protected:
+       void setNotes(int array[4]);
+};
+
+
+
+#endif
diff --git a/viola.png b/viola.png
new file mode 100644 (file)
index 0000000..ab5071b
Binary files /dev/null and b/viola.png differ
diff --git a/violin.png b/violin.png
new file mode 100644 (file)
index 0000000..8e7d029
Binary files /dev/null and b/violin.png differ
diff --git a/vtones.pro b/vtones.pro
new file mode 100644 (file)
index 0000000..bf5a28b
--- /dev/null
@@ -0,0 +1,24 @@
+TEMPLATE = app
+INCLUDEPATH += .
+DEFINES += QT_DLL
+
+HEADERS += \
+       pianoinstrument.h \
+       stringinstrument.h \
+       instrument.h \
+       mainwin.h \
+       midiengine.h
+
+SOURCES += \
+       pianoinstrument.cpp \
+       stringinstrument.cpp \
+       instrument.cpp \
+       mainwin.cpp \
+       midiengine.cpp \
+       main.cpp \
+
+
+MOC_DIR = tmp
+OBJECTS_DIR = tmp
+
+