--- /dev/null
+ 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
--- /dev/null
+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
+
+
--- /dev/null
+// 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;
+}
+
+
--- /dev/null
+// 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
--- /dev/null
+// 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();
+}
+
+
--- /dev/null
+// 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);
+
+}
+
--- /dev/null
+// 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
--- /dev/null
+// 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;
+}
+
+
--- /dev/null
+// 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
+
--- /dev/null
+// 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 "Octave" 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();
+}
+
--- /dev/null
+// 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
--- /dev/null
+// 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();
+}
--- /dev/null
+// 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
--- /dev/null
+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
+
+