diff --git a/V203F6P6/midi/Makefile b/V203F6P6/midi/Makefile new file mode 100644 index 0000000..d1a4971 --- /dev/null +++ b/V203F6P6/midi/Makefile @@ -0,0 +1,65 @@ +# ch32v203 +TARGET?= ch32v203 +TOOL ?= gcc +#TOOL ?= clang + +PRJ = example + +VPATH = . ./$(TARGET) +BLD = ./build/ +DFLAGS = -d +LFLAGS = -g +LDLIBS = -L./$(TARGET)/usbd -lusbd +BFLAGS = --strip-unneeded + +CFLAGS = -MMD -Wall -ggdb -fno-exceptions -ffunction-sections -fdata-sections +CFLAGS+= -I. -I./common -I./$(TARGET) -I./$(TARGET)/usbd +DEL = rm -f + +# zdrojaky +OBJS = main.o hack.o pwmclass.o +OBJS += tone.o midiplayer.o miditone.o +OBJS += spiblocked.o norflash.o +OBJS += intelhex.o linkprotocol.o usb_desc.o + +include $(TARGET)/$(TOOL).mk +BOBJS = $(addprefix $(BLD),$(OBJS)) + +all: $(BLD) $(PRJ).elf +# ... atd. +-include $(BLD)*.d +# linker +$(PRJ).elf: $(BOBJS) + -@echo [LD $(TOOL),$(TARGET)] $@ + @$(LD) $(LFLAGS) -o $(PRJ).elf $(BOBJS) $(LDLIBS) + -@echo "size:" + @$(SIZE) $(PRJ).elf + -@echo "listing:" + $(DUMP) $(DFLAGS) $(PRJ).elf > $(PRJ).lst + -@echo "OK." + $(COPY) $(BFLAGS) -O binary $(PRJ).elf $(PRJ).bin + $(COPY) $(BFLAGS) -O ihex $(PRJ).elf $(PRJ).hex +# preloz co je potreba +$(BLD)%.o: %.c + -@echo [CC $(TOOL),$(TARGET)] $@ + @$(CC) -c $(CFLAGS) $< -o $@ +$(BLD)%.o: %.cpp + -@echo [CX $(TOOL),$(TARGET)] $@ + @$(CXX) -std=c++17 -fno-rtti -c $(CFLAGS) $< -o $@ +$(BLD): + mkdir $(BLD) +flash: $(PRJ).elf + minichlink -w $(PRJ).bin flash -b + +miditone.c: ton/gen + ton/gen +ton/gen: ton/gen.cpp + g++ -Os ton/gen.cpp -o ton/gen + +# vycisti +clean: + $(DEL) $(BLD)* *.lst *.bin *.hex *.elf *.map *~ miditone.c +distclean: clean + $(DEL) ton/gen + cd ./img && make distclean +.PHONY: all clean distclean flash diff --git a/V203F6P6/midi/audio.h b/V203F6P6/midi/audio.h new file mode 100644 index 0000000..defc157 --- /dev/null +++ b/V203F6P6/midi/audio.h @@ -0,0 +1,16 @@ +#ifndef AUDIO_H +#define AUDIO_H + +#include + +static constexpr int AudioSampleRate = 24000; +/// Počet generátorů. +static constexpr unsigned int maxGens = 12; +/// Kladné maximum vzorku. +static constexpr int maxValue = 30000; +/// Záporné maximum vzorku. +static constexpr int minValue = -maxValue; +/// +static constexpr unsigned int maxAmplt = (1U<<27); + +#endif // AUDIO_H diff --git a/V203F6P6/midi/cache.h b/V203F6P6/midi/cache.h new file mode 100644 index 0000000..2625578 --- /dev/null +++ b/V203F6P6/midi/cache.h @@ -0,0 +1,39 @@ +#ifndef _CACHE_H_DEF +#define _CACHE_H_DEF +#include "norflash.h" + +template class Cache { + T data [N]; + unsigned begin_address; + unsigned ofset_current; + NorFlash nor; + public: + explicit Cache () noexcept : begin_address(0u), ofset_current(0u), nor() {} + Cache & operator= (const unsigned addr) { + begin_address = addr; + ofset_current = 0u; + reload (); + return * this; + } + operator bool () { if (begin_address == 0u) return false; return true; } + T * operator++ (int) { + check_validity (); + T * tmp = data + ofset_current; + ofset_current += 1; + return tmp; + } + protected: + void check_validity () { + if (ofset_current >= N) { + begin_address += N * sizeof(T); + reload (); + ofset_current = 0u; + } + } + void reload () { + unsigned char * ptr = reinterpret_cast(data); + nor.ReadBlock (begin_address, ptr, N * sizeof(T)); + } +}; + +#endif // _CACHE_H_DEF diff --git a/V203F6P6/midi/ch32v203 b/V203F6P6/midi/ch32v203 new file mode 120000 index 0000000..7650c85 --- /dev/null +++ b/V203F6P6/midi/ch32v203 @@ -0,0 +1 @@ +../ch32v203/ \ No newline at end of file diff --git a/V203F6P6/midi/common b/V203F6P6/midi/common new file mode 120000 index 0000000..8332399 --- /dev/null +++ b/V203F6P6/midi/common @@ -0,0 +1 @@ +../common/ \ No newline at end of file diff --git a/V203F6P6/midi/endien.h b/V203F6P6/midi/endien.h new file mode 100644 index 0000000..b0f2353 --- /dev/null +++ b/V203F6P6/midi/endien.h @@ -0,0 +1,112 @@ +#ifndef ENDIEN_H +#define ENDIEN_H +#include +/** + @file + @brief Pořadí bytů ve slově. + +*/ +#if __GNUC__ < 4 // Compiler test +#error "Zkontroluj nastaveni" +#endif +// Pro 16.bit musíme struktury pakovat +#define PACKSTRUCT __attribute__((packed)) + +#if __thumb__ + // Specificky a krátce pro danou architektutu a pokud chceme mít úplnou kontrolu. + static inline uint32_t swab32 (uint32_t p) { + register uint32_t t; + asm volatile ("rev %0, %1":"=r"(t):"0"(p)); + return t; + } +#else //__thumb__ + // Obecně + static inline uint32_t swab32 (uint32_t p) { + // return __builtin_bswap32 (p); // I takto to jde, ale pro názornost: + return ((p & 0x000000ff) << 24) + | ((p & 0x0000ff00) << 8) + | ((p & 0x00ff0000) >> 8) + | ((p & 0xff000000) >> 24); + } +#endif //__thumb__ +// Tohle nebudeme ani specifikovat pro ARM +static inline uint16_t swab16 (uint16_t p) { + return (p << 8) | (p >> 8); +} +// Nedělá nic a ani nijak neotravuje - pouze pro kompatibilitu +static inline uint32_t nosw32 (uint32_t p) { + return p; +} +static inline uint16_t nosw16 (uint16_t p) { + return p; +} +// Specificky podle pořadí bytů - LE je pro ARM i X86 normální. +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define cpu_to_be32 swab32 + #define be32_to_cpu swab32 + #define cpu_to_le32 nosw32 + #define le32_to_cpu nosw32 + + #define cpu_to_be16 swab16 + #define be16_to_cpu swab16 + #define cpu_to_le16 nosw16 + #define le16_to_cpu nosw16 +#else + #define cpu_to_be32 nosw32 + #define be32_to_cpu nosw32 + #define cpu_to_le32 swab32 + #define le32_to_cpu swab32 + + #define cpu_to_be16 nosw16 + #define be16_to_cpu nosw16 + #define cpu_to_le16 swab16 + #define le16_to_cpu swab16 +#endif +/** + @class ui32be + @brief C++ 32.bit data v BIG Endien. + @class ui32le + @brief C++ 32.bit data v LITTLE Endien. + @class ui16be + @brief C++ 16.bit data v BIG Endien. + @class ui16le + @brief C++ 16.bit data v LITTLE Endien. +*/ +class ui32be { + public: + /// Nastavení hodnoty + /// @par p uint32_t hodnota + void set (uint32_t p) {num = cpu_to_be32 (p); }; + /// Zjištění hodnoty + /// @return uint32_t hodnota + uint32_t get (void) {return be32_to_cpu (num); }; + private: + /// Vlastní data uint32_t - k nim se přímo nedostaneme, jsou privátní + uint32_t num; +}PACKSTRUCT; + +class ui32le { + public: + void set (uint32_t p) {num = cpu_to_le32 (p); }; + uint32_t get (void) {return le32_to_cpu (num); }; + private: + uint32_t num; +}PACKSTRUCT; +/* *******************************************************/ +class ui16be { + public: + void set (uint16_t p) {num = cpu_to_be16 (p); }; + uint16_t get (void) {return be16_to_cpu (num); }; + private: + uint16_t num; +}PACKSTRUCT; + +class ui16le { + public: + void set (uint16_t p) {num = cpu_to_le16 (p); }; + uint16_t get (void) {return le16_to_cpu (num); }; + private: + uint16_t num; +}PACKSTRUCT; + +#endif // ENDIEN_H diff --git a/V203F6P6/midi/hack.c b/V203F6P6/midi/hack.c new file mode 100644 index 0000000..391b7bf --- /dev/null +++ b/V203F6P6/midi/hack.c @@ -0,0 +1,21 @@ +#include +#include +typedef __SIZE_TYPE__ size_t; +size_t strlen (const char *s) { + size_t l = 0; + while (*s++) l++; + return l; +} +void *memcpy (void *dest, const void *src, size_t n) { + const char *s = (const char *) src; + char *d = (char *) dest; + int i; + for (i=0; i +#include "norflash.h" +#include "fifo.h" +#include "baselayer.h" + +class MemoryBase { + NorFlash flash; + public: + explicit MemoryBase () noexcept : flash() {} + uint32_t Write (const uint32_t addr, const void * ptr, const uint32_t len) { + return flash.WriteBlock(addr, reinterpret_cast(ptr), len); + } + uint32_t Read (const uint32_t addr, void * ptr, const uint32_t len) { + return flash.ReadBlock(addr, reinterpret_cast (ptr), len); + } + void Erase (const uint32_t blok) { + /* Flash používá 24-bitovou adresu počátku bloku, zde máme k dispozici + * jen 16-bitový offset. Takže to jen natáhneme 256 krát. Software + * tomu musí odpovídat. + * */ + flash.EraseSector(blok << 8); + } +}; + +class CdcCmd : public BaseLayer { + static constexpr int maxlen = 0x80; + FIFO ring; + char line [maxlen]; + unsigned index; + public: + explicit CdcCmd () noexcept : BaseLayer(), ring(), index(0u) {} + uint32_t Up(const char * data, const uint32_t len) override { + for (unsigned n=0u; n 0x20u ? 0x20u : res; + const unsigned n = Down (ptr + ofs, chunk); + ofs += n; + res -= n; + } + } + char * GetLine (unsigned & len) { + char c; + while (ring.Read(c)) { + line [index++] = c; + if (c == '\n') { + len = index; + index = 0u; + return line; + } + } + len = 0u; + return line; + } +}; +#endif // HELPERS_H diff --git a/V203F6P6/midi/img/Makefile b/V203F6P6/midi/img/Makefile new file mode 100644 index 0000000..fe0c16e --- /dev/null +++ b/V203F6P6/midi/img/Makefile @@ -0,0 +1,31 @@ +PR = image +VPATH = . +CXX = g++ +CC = gcc +CFLAGS = -Wall -Os -I.. +MFLAGS = -o $(PR) +LFLAGS = + +all: data.bin $(PR) + +OBJECTS = main.o +# fakticky není potřeba +$(PR): $(OBJECTS) + $(CXX) $(MFLAGS) $(OBJECTS) $(LFLAGS) +clean: + rm -f *.o *~ data.elf data.map $(PR) extdata.c +distclean: clean + rm -f data.bin miditones melody.c +%.o: %.cpp + $(CXX) -std=c++14 -c $(CFLAGS) -o $@ $< +%.o: %.c + $(CC) -c $(CFLAGS) -o $@ $< +data.elf: melody.c + riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -Wall -Os -I.. -fdata-sections -Wl,-Map=data.map,--gc-sections -nostdlib -nostartfiles -o data.elf melody.c -L. -T script.ld +data.bin: data.elf + riscv64-unknown-elf-objcopy -O binary data.elf data.bin +melody.c: miditones + ./miditones -d -s2 -t12 ../mid/ +miditones: miditonesV1.6.c + gcc -Os -Wno-pointer-sign -Wno-return-type miditonesV1.6.c -o miditones +.PHONY: all clean diff --git a/V203F6P6/midi/img/main.cpp b/V203F6P6/midi/img/main.cpp new file mode 100644 index 0000000..831f87a --- /dev/null +++ b/V203F6P6/midi/img/main.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +/* Jedna z možností jak dostat data do externí flash. Sice to poněkud + * nabobtná (na začátku je zbytečně celá struktura SayedTexts + padding), + * ale funguje to. + * 1. zdroják melofy.c se přeloží a pomocí linker skriptu se vytvoří elf + * 2. z elf se udělá binárka, příp. hex soubor standardním binutils + * 3. binárka se tímto přečte a do extdata.c se extrahují potřebné informace. + * extdata.c pak obsahuje adresy meloddií potřebné pro čtení. + * Nezávisí to na tom, zda se binárka vytváří na 64. nebo 32. bit stroji. */ +class Reader { + unsigned char * file_data; + int file_size; + public: + explicit Reader() : file_data(nullptr), file_size(0) {} + ~Reader() { + if (file_data) delete [] file_data; + } + int process (const char * filename); + protected: + void check (); + void generate(); +}; +int Reader::process(const char * filename) { + struct stat statbuf; + int r = ::stat(filename, &statbuf); + if (r < 0) return -1; + if (file_data) delete [] file_data; + file_data = new unsigned char [statbuf.st_size]; + FILE * in_file = fopen(filename, "r"); + r = fread (file_data, 1, statbuf.st_size, in_file); + file_size = r; + printf("readen %d bytes (%g 0x1000 blocks)\n", file_size, double(file_size)/4096.0); + if (!in_file) return -1; + fclose(in_file); + check(); // kontrola - mělo by sedět to co je v data.bin a přeložené melody.c + generate(); // generuje C-soubor s adresami a počty rámců + return 0; +} +void Reader::check() { + if (!file_size) return; +} +// Jen toto je podstatné - vytvoření extdata.c +static const char * prefix = R"---(/* GENERATED FILE DO NOT EDIT */ +const unsigned scores [] = {)---"; + +void Reader::generate() { + FILE * out = fopen("extdata.c","w"); + fprintf(out, "%s", prefix); + + uint32_t * ptr = reinterpret_cast(file_data); + for (unsigned n=0; ; n++) { + if ((n%8) == 0) fprintf(out, "\n"); + const size_t e = ptr[n]; + fprintf(out, " 0x%06lXu,", e); + if (e == 0lu) break; + } + fprintf(out, "\n};\n"); + fclose(out); +} + +/// main () +int main (int argc, char *argv[]) { + Reader read; + return read.process("data.bin"); +} diff --git a/V203F6P6/midi/img/miditonesV1.6.c b/V203F6P6/midi/img/miditonesV1.6.c new file mode 100644 index 0000000..febd967 --- /dev/null +++ b/V203F6P6/midi/img/miditonesV1.6.c @@ -0,0 +1,1000 @@ +/********************************************************************************* +* +* MIDITONES +* +* Convert a MIDI file into a bytestream of notes +* +* +* (C) Copyright 2011, Len Shustek +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of version 3 of the GNU General Public License as +* published by the Free Software Foundation at http://www.gnu.org/licenses, +* with Additional Permissions under term 7(b) that the original copyright +* notice and author attibution must be preserved and under term 7(c) that +* modified versions be marked as different from the original. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +***********************************************************************************/ +/* +* Change log +* 19 January 2011, L.Shustek, V1.0 +* -Initial release. +* 26 February 2011, L. Shustek, V1.1 +* -Expand the documentation generated in the output file. +* -End the binary output file with an "end of score" command. +* -Fix bug: Some "stop note" commands were generated too early. +* 04 March 2011, L. Shustek, V1.2 +* -Minor error message rewording. +* 13 June 2011, L. Shustek, V1.3 +* -Add -s2 strategy to try to keep each track on its own tone generator +* for when there are separate speakers. This obviously works only when +* each track is monophonic. (Suggested by Michal Pustejovsky) +* 20 November 2011, L. Shustek, V1.4 +* -Add -cn option to mask which channels (tracks) to process +* -Add -kn option to change key +* Both of these are in support of music-playing on my Tesla Coil. +* 05 December 2011, L. Shustek, V1.5 +* -Fix command line parsing error for option -s1 +* -Display the commandline in the C file output +* -Change to decimal instead of hex for note numbers in the C file output +* 06 August 2013, L. Shustek, V1.6 +* -Changed to allow compilation and execution in 64-bit environments +* by using C99 standard intN_t and uintN_t types for MIDI structures, +* and formatting specifications like "PRId32" instead of "ld". +*/ + +#define VERSION "1.6" + + +/*-------------------------------------------------------------------------------- +* +* +* About MIDITONES +* +* +* MIDITONES converts a MIDI music file into a much simplified stream of commands, +* so that a version of the music can be played on a synthesizer having only +* tone generators without any volume or tone controls. +* +* Volume ("velocity") and instrument specifications in the MIDI files are discarded. +* All the tracks are prcoessed and merged into a single time-ordered stream of +* "note on", "note off", and "delay" commands. +* +* This was written for the "Playtune" Arduino library, which plays polyphonic music +* using up to 6 tone generators run by the timers on the processor. See the separate +* documentation for Playtune. But MIDITONES may prove useful for other tone +* generating systems. +* +* The output can be either a C-language source code fragment that initializes an +* array with the command bytestream, or a binary file with the bytestream itself. +* +* MIDITONES is written in standard ANSI C (plus strlcpy and strlcat functions), and +* is meant to be executed from the command line. There is no GUI interface. +* +* The MIDI file format is complicated, and this has not been tested on a very +* wide variety of file types. In particular, we have tested only format type "1", +* which seems to be what most of them are. Let me know if you find MIDI files +* that it won't digest and I'll see if I can fix it. + +* This has been tested only on a little-endian PC, but I think it should work on +* big-endian processors too. Note that the MIDI file format is inherently +* big-endian. +* +* +* ***** The command line ***** +* +* To convert a MIDI file called "chopin.mid" into a command bytestream, execute +* +* miditones chopin +* +* It will create a file in the same directory called "chopin.c" which contains +* the C-language statement to intiialize an array called "score" with the bytestream. +* +* +* The general form for command line execution is this: +* +* miditones [-p] [-lg] [-lp] [-s1] [-tn] [-b] [-cn] [-kn] +* +* The is the base name, without an extension, for the input and +* output files. It can contain directory path information, or not. +* +* The input file is the base name with the extension ".mid". The output filename(s) +* are the base name with ".c", ".bin", and/or ".log" extensions. +* +* +* The following command-line options can be specified: +* +* -p Only parse the MIDI file; don't generate an output file. +* Tracks are processed sequentially instead of being merged into chronological order. +* This is mostly useful when generating a log to debug MIDI file parsing problems. +* +* -lp Log input file parsing information to the .log file +* +* -lg Log output bytestream generation information to the .log file +* +* -sn Use bytestream generation strategy "n". +* Two strategies are currently implemented: +* 1: favor track 1 notes instead of all tracks equally +* 2: try to keep each track to its own tone generator +* +* -tn Generate the bytestream so that at most n tone generators are used. +* The default is 6 tone generators, and the maximum is 16. +* The program will report how many notes had to be discarded because there +* weren't enough tone generators. Note that for the Arduino Playtunes +* library, it's ok to have the bytestream use more tone genreators than +* exist on your processor because any extra notes will be ignored, although +* it does make the file bigger than necessary . Of course, too many ignored +* notes will make the music sound really strange! +* +* -b Generate a binary file with the name .bin, instead of a +* C-language source file with the name .c. +* +* -cn Only process the channel numbers whose bits are on in the number "n". +* For example, -c3 means "only process channels 0 and 1" +* +* -kn Change the musical key of the output by n chromatic notes. +* -k-12 goes one octave down, -k12 goes one octave up, etc. +* +* +* ***** The score bytestream ***** +* +* The generated bytestream is a series of commands that turn notes on and off, and +* start delays until the next note change. Here are the details, with numbers +* shown in hexadecimal. +* +* If the high-order bit of the byte is 1, then it is one of the following commands: +* +* 9t nn Start playing note nn on tone generator t. Generators are numbered +* starting with 0. The notes numbers are the MIDI numbers for the chromatic +* scale, with decimal 60 being Middle C, and decimal 69 being Middle A (440 Hz). +* +* 8t Stop playing the note on tone generator t. +* +* F0 End of score: stop playing. +* +* E0 End of score: start playing again from the beginning. +* (Shown for completeness; MIDITONES won't generate this.) +* +* If the high-order bit of the byte is 0, it is a command to delay for a while until +* the next note change.. The other 7 bits and the 8 bits of the following byte are +* interpreted as a 15-bit big-endian integer that is the number of milliseconds to +* wait before processing the next command. For example, +* +* 07 D0 +* +* would cause a delay of 0x07d0 = 2000 decimal millisconds, or 2 seconds. Any tones +* that were playing before the delay command will continue to play. +* +* +* Len Shustek, 4 Feb 2011 +* +*----------------------------------------------------------------------------------*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/*********** MIDI file header formats *****************/ + +struct midi_header { + int8_t MThd[4]; + uint32_t header_size; + uint16_t format_type; + uint16_t number_of_tracks; + uint16_t time_division; +}; + +struct track_header { + int8_t MTrk[4]; + uint32_t track_size; +}; + + +/*********** Global variables ******************/ + +#define MAX_TONEGENS 16 /* max tone generators: tones we can play simultaneously */ +#define DEFAULT_TONEGENS 6 /* default number of tone generators */ +#define MAX_TRACKS 24 /* max number of MIDI tracks we will process */ + +bool loggen, logparse, parseonly, strategy1, strategy2, binaryoutput; +FILE *infile, *outfile, *logfile; +uint8_t *buffer, *hdrptr; +unsigned long buflen; +int num_tracks; +int tracks_done = 0; +int outfile_itemcount = 0; +int num_tonegens = DEFAULT_TONEGENS; +int num_tonegens_used = 0; +unsigned channel_mask = 0xffff; // bit mask of channels to process +int keyshift = 0; // optional chromatic note shift for output file +long int outfile_bytecount = 0; +unsigned int ticks_per_beat = 240; +unsigned long timenow = 0; +unsigned long tempo; /* current tempo in usec/qnote */ + +unsigned int directory = 0; +unsigned int file_count = 0; + +struct tonegen_status { /* current status of a tone generator */ + bool playing; /* is it playing? */ + int track; /* if so, which track is the note from? */ + int note; /* what note is playing? */ +} +tonegen [MAX_TONEGENS] = { + 0}; + +struct track_status { /* current processing point of a MIDI track */ + uint8_t *trkptr; /* ptr to the next note change */ + uint8_t *trkend; /* ptr past the end of the track */ + unsigned long time; /* what time we're at in the score */ + unsigned long tempo; /* the tempo last set, in usec/qnote */ + unsigned int preferred_tonegen; /* for strategy2: try to use this generator */ + unsigned char cmd; /* CMD_xxxx next to do */ + unsigned char note; /* for which note */ + unsigned char last_event; /* the last event, for MIDI's "running status" */ + bool tonegens[MAX_TONEGENS];/* which tone generators our notes are playing on */ +} +track[MAX_TRACKS] = { + 0}; + + +/* output bytestream commands, which are also stored in track_status.cmd */ + +#define CMD_PLAYNOTE 0x90 /* play a note: low nibble is generator #, note is next byte */ +#define CMD_STOPNOTE 0x80 /* stop a note: low nibble is generator # */ +#define CMD_RESTART 0xe0 /* restart the score from the beginning */ +#define CMD_STOP 0xf0 /* stop playing */ +/* if CMD < 0x80, then the other 7 bits and the next byte are a 15-bit number of msec to delay */ + +/* these other commands stored in the track_status.com */ +#define CMD_TEMPO 0xFE /* tempo in usec per quarter note ("beat") */ +#define CMD_TRACKDONE 0xFF /* no more data left in this track */ + + + +/************** command-line processing *******************/ + +void SayUsage(char *programName){ + static char *usage[] = { + "Convert MIDI files to an Arduino PLAYTUNE bytestream", + "miditones [-p] [-lg] [-lp] [-s1] [-tn] ", + " -p parse only, don't generate bytestream", + " -lp log input parsing", + " -lg log output generation", + " -s1 strategy 1: favor track 1", + " -s2 strategy 2: try to assign tracks to specific tone generators", + " -tn use at most n tone generators (default is 6, max is 16)", + " -b binary file output instead of C source text", + " -cn mask for which tracks to process, e.g. -c3 for only 0 and 1", + " -kn key shift in chromatic notes, positive or negative", + "input file: .mid", + "output file: .bin or .c", + "log file: .log", + "" }; + int i=0; + while (usage[i][0] != '\0') fprintf(stderr, "%s\n", usage[i++]); +} + +int HandleOptions(int argc,char *argv[]) { + /* returns the index of the first argument that is not an option; i.e. + does not start with a dash or a slash*/ + + int i,firstnonoption=0; + + /* --- The following skeleton comes from C:\lcc\lib\wizard\textmode.tpl. */ + for (i=1; i< argc;i++) { + if (argv[i][0] == '/' || argv[i][0] == '-') { + switch (toupper(argv[i][1])) { + case 'H': + case '?': + SayUsage(argv[0]); + exit(1); + case 'L': + if (toupper(argv[i][2]) == 'G') loggen = true; + else if (toupper(argv[i][2]) == 'P') logparse = true; + else goto opterror; + break; + case 'P': + parseonly = true; + break; + case 'B': + binaryoutput = true; + break; + case 'D': + directory = true; + break; + case 'S': + if (argv[i][2] == '1') strategy1 = true; + else if (argv[i][2] == '2') strategy2 = true; + else goto opterror; + break; + case 'T': + if (sscanf(&argv[i][2],"%d",&num_tonegens) != 1 || num_tonegens <1 || num_tonegens > MAX_TONEGENS) goto opterror; + printf("Using %d tone generators.\n", num_tonegens); + break; + case 'C': + if (sscanf(&argv[i][2],"%d",&channel_mask) != 1 || channel_mask > 0xffff) goto opterror; + printf("Channel (track) mask is %04X.\n", channel_mask); + break; + case 'K': + if (sscanf(&argv[i][2],"%d",&keyshift) != 1 || keyshift < -100 || keyshift > 100) goto opterror; + printf("Using keyshift %d.\n", keyshift); + break; + + /* add more option switches here */ +opterror: + default: + fprintf(stderr,"unknown option: %s\n",argv[i]); + SayUsage(argv[0]); + exit(4); + } + } + else { + firstnonoption = i; + break; + } + } + return firstnonoption; +} + +void print_command_line (int argc,char *argv[]) { + int i; + fprintf(outfile, "// command line: "); + for (i=0; i< argc; i++) fprintf(outfile,"%s ",argv[i]); + fprintf(outfile, "\n"); +} + + + +/**************** utility routines **********************/ + +/* match a constant character sequence */ + +int charcmp (char *buf, char *match) { + int len, i; + len = strlen (match); + for (i=0; i MIDI file error at position %04X (%d): %s\n", (uint16_t)(bufptr-buffer), (uint16_t)(bufptr-buffer), msg); + /* print some bytes surrounding the error */ + ptr = bufptr - 16; + if (ptr < buffer) ptr = buffer; + for (; ptr <= bufptr+16 && ptr < buffer+buflen; ++ptr) fprintf (stderr, ptr==bufptr ? " [%02X] ":"%02X ", *ptr); + fprintf(stderr, "\n"); + exit(8); +} + +/* check that we have a specified number of bytes left in the buffer */ + +void chk_bufdata(unsigned char *ptr, int len) { + if (ptr + len - buffer > buflen) midi_error("data missing", ptr); +} + + +/* fetch big-endian numbers */ + +uint16_t rev_short (uint16_t val) { + return ((val&0xff)<<8) | ((val>>8)&0xff); +} + +uint32_t rev_long (uint32_t val){ + return (((rev_short((uint16_t)val) & 0xffff) << 16) | + (rev_short((uint16_t)(val >> 16)) & 0xffff)); +} + +/* account for new items in the non-binary output file +and generate a newline every so often. */ + +void outfile_items (int n) { + outfile_bytecount += n; + outfile_itemcount += n; + if (!binaryoutput && outfile_itemcount > 20) { + fprintf (outfile, "\n"); + outfile_itemcount = 0; + } +} + +/************** process the MIDI file header *****************/ + +void process_header (void) { + struct midi_header *hdr; + unsigned int time_division; + + chk_bufdata(hdrptr, sizeof(struct midi_header)); + hdr = (struct midi_header *) hdrptr; + if (!charcmp(hdr->MThd,"MThd")) midi_error("Missing 'MThd'", hdrptr); + + num_tracks = rev_short(hdr->number_of_tracks); + + time_division = rev_short(hdr->time_division); + if (time_division < 0x8000) ticks_per_beat = time_division; + else ticks_per_beat = ((time_division >> 8) & 0x7f) /* SMTE frames/sec */ * (time_division & 0xff); /* ticks/SMTE frame */ + + if (logparse) { + fprintf (logfile, "Header size %" PRId32 "\n", rev_long(hdr->header_size)); + fprintf (logfile, "Format type %d\n", rev_short(hdr->format_type)); + fprintf (logfile, "Number of tracks %d\n", num_tracks); + fprintf (logfile, "Time division %04X\n", time_division); + fprintf (logfile, "Ticks/beat = %d\n", ticks_per_beat); + + } + hdrptr += rev_long(hdr->header_size) + 8; /* point past header to track header, presumably. */ + return; +} + + +/**************** Process a MIDI track header *******************/ + +void start_track (int tracknum) { + struct track_header *hdr; + unsigned long tracklen; + + chk_bufdata(hdrptr, sizeof(struct track_header)); + hdr = (struct track_header *) hdrptr; + if (!charcmp(hdr->MTrk,"MTrk")) midi_error("Missing 'MTrk'", hdrptr); + + tracklen = rev_long(hdr->track_size); + if (logparse) fprintf (logfile, "\nTrack %d length %ld\n", tracknum, tracklen); + hdrptr += sizeof (struct track_header); /* point past header */ + chk_bufdata(hdrptr, tracklen); + track[tracknum].trkptr = hdrptr; + hdrptr += tracklen; /* point to the start of the next track */ + track[tracknum].trkend = hdrptr; /* the point past the end of the track */ +} + + +/* Get a MIDI-style variable-length integer */ + +unsigned long get_varlen (uint8_t **ptr) { + /* Get a 1-4 byte variable-length value and adjust the pointer past it. + These are a succession of 7-bit values with a MSB bit of zero marking the end */ + + unsigned long val; + int i, byte; + + val = 0; + for (i=0; i<4; ++i){ + byte = *(*ptr)++; + val = (val<<7) | (byte&0x7f); + if (!(byte&0x80)) return val; + } + return val; +} + + +/*************** Process the MIDI track data ***************************/ + +/* Skip in the track for the next "note on", "note off" or "set tempo" command, +then record that information in the track status block and return. */ + +void find_note (int tracknum) { + unsigned long int delta_time; + int event, chan; + int i; + int note, velocity, parm; + int meta_cmd, meta_length; + unsigned long int sysex_length; + struct track_status *t; + + /* process events */ + + t = &track[tracknum]; /* our track status structure */ + while (t->trkptr < t->trkend) { + + delta_time = get_varlen(&t->trkptr); + if (logparse) { + fprintf (logfile, "trk %d ", tracknum); + fprintf (logfile, delta_time ? "delta time %4ld, " : " ", delta_time); + } + t->time += delta_time; + + if (*t->trkptr < 0x80) /* "running status" */ event = t->last_event;/* means same event as before */ + else { /* new "status" (event type) */ + event = *t->trkptr++; + t->last_event = event; + } + + if (event == 0xff) { /* meta-event */ + meta_cmd = *t->trkptr++; + meta_length = *t->trkptr++; + switch (meta_cmd) { + case 0x2f: + if (logparse) fprintf(logfile, "end of track\n"); + break; + case 0x00: + if (logparse) fprintf(logfile, "sequence number %d\n", rev_short(*(unsigned short *)t->trkptr)); + break; + case 0x20: + if (logparse) fprintf(logfile, "channel prefix %d\n", *t->trkptr); + break; + case 0x51: /* tempo: 3 byte big-endian integer! */ + t->cmd = CMD_TEMPO; + t->tempo = rev_long(*(unsigned long *)(t->trkptr-1)) & 0xffffffL; + if (logparse) fprintf(logfile, "set tempo %ld usec/qnote\n", t->tempo); + t->trkptr += meta_length; + return; + case 0x54: + if (logparse) fprintf(logfile, "SMPTE offset %08" PRIx32 "\n", rev_long(*(unsigned long *)t->trkptr)); + break; + case 0x58: + if (logparse) fprintf(logfile, "time signature %08" PRIx32 "\n", rev_long(*(unsigned long *)t->trkptr)); + break; + case 0x59: + if (logparse) fprintf(logfile, "key signature %04X\n", rev_short(*(unsigned short *)t->trkptr)); + break; + default: /* assume it is a string */ + if (logparse) { + fprintf(logfile, "meta cmd %02X, length %d, \"", meta_cmd, meta_length); + for (i=0; itrkptr[i]; + fprintf(logfile, "%c", isprint(ch) ? ch : '?'); + } + fprintf(logfile, "\"\n"); + } + if (tracknum==0 && meta_cmd==0x03 && !parseonly && !binaryoutput) { + /* Incredibly, MIDI has no standard for recording the name of the piece! + Track 0's "trackname" (meta 0x03) is sometimes used for that, so + we output it to the C file as documentation. */ + fprintf(outfile, "// "); + for (i=0; itrkptr[i]; + fprintf(outfile, "%c", isprint(ch) ? ch : '?'); + } + fprintf(outfile, "\n"); + } + break; + } + t->trkptr += meta_length; + } + + else if (event <0x80) midi_error("Unknown MIDI event type", t->trkptr); + + else { + chan = event & 0xf; + switch (event>>4) { + case 0x8: + t->note = *t->trkptr++; + velocity = *t->trkptr++; +note_off: + if (logparse) fprintf (logfile, "note %02X off, chan %d, velocity %d\n", t->note, chan, velocity); + if ((1<cmd = CMD_STOPNOTE; + return; /* stop processing and return */ + } + break; // else keep looking + case 0x9: + t->note = *t->trkptr++; + velocity = *t->trkptr++; + if (velocity == 0) /* some scores use note-on with zero velocity for off! */ goto note_off; + if (logparse) fprintf (logfile, "note %02X on, chan %d, velocity %d\n", t->note, chan, velocity); + if ((1<cmd = CMD_PLAYNOTE; + return; /* stop processing and return */ + } + break; // else keep looking + case 0xa: + note = *t->trkptr++; + velocity = *t->trkptr++; + if (logparse) fprintf (logfile, "after-touch %02X, %02X\n", note, velocity); + break; + case 0xb: + note = *t->trkptr++; + velocity = *t->trkptr++; + if (logparse) fprintf (logfile, "control change %02X, %02X\n", note, velocity); + break; + case 0xc: + note = *t->trkptr++; + if (logparse) fprintf(logfile, "program patch %02X\n", note); + break; + case 0xd: + chan = *t->trkptr++; + if (logparse) fprintf(logfile, "channel after-touch %02X\n", chan); + break; + case 0xe: + note = *t->trkptr++; + velocity = *t->trkptr++; + if (logparse) fprintf(logfile, "pitch wheel change %02X, %02X\n", note, velocity); + break; + case 0xf: + sysex_length = get_varlen(&t->trkptr); + if (logparse) fprintf(logfile, "SysEx event %02X, %ld bytes\n", event, sysex_length); + t->trkptr += sysex_length; + break; + default: + midi_error("Unknown MIDI command", t->trkptr); + } + } + } + t->cmd = CMD_TRACKDONE; /* no more notes to process */ + ++tracks_done; +} +#define MAXPATH 120 + +int ProcessMidiFile (char* filebasename) { + int i; + int tracknum = 0; + int earliest_tracknum = 0; + unsigned long earliest_time = 0; + int notes_skipped = 0; + + char filename[MAXPATH]; + // set static variables to default + outfile_bytecount = 0; + outfile_itemcount = 0; + tracks_done = 0; + num_tonegens_used = 0; + timenow = 0; + tempo = 0; + ticks_per_beat = 240; + channel_mask = 0xffff; + memset (track, 0, sizeof (track)); + memset (tonegen, 0, sizeof (tonegen)); + /* Open the input file */ + + strncpy(filename, filebasename, MAXPATH); + strncat(filename, ".mid", MAXPATH); + infile = fopen(filename, "rb"); + if (!infile) { + fprintf(stderr, "Unable to open input file %s", filename); + return 1; + } + + /* Read the whole input file into memory */ + + fseek(infile, 0, SEEK_END); /* find file size */ + buflen = ftell(infile); + fseek(infile, 0, SEEK_SET); + + buffer = (unsigned char *) malloc (buflen+1); + if (!buffer) { + fprintf(stderr, "Unable to allocate %ld bytes for the file", buflen); + return 1; + } + + i = fread(buffer, buflen, 1, infile); + fclose(infile); + if (logparse) fprintf(logfile, "Processing %s, %ld bytes\n", filename, buflen); + + /* Create the output file */ + + if (!parseonly) { + /* + strncpy(filename, filebasename, MAXPATH); + if (binaryoutput) { + strncat(filename, ".bin", MAXPATH); + outfile = fopen(filename, "wb"); + } + else { + strncat(filename, ".c", MAXPATH); + outfile = fopen(filename, "w"); + } + if (!outfile) { + fprintf(stderr, "Unable to open output file %s", filename); + return 1; + } + */ + if (!binaryoutput) { /* create header of C file that initializes score data */ + time_t rawtime; + struct tm *ptime; + time (&rawtime); + fprintf(outfile, "// Playtune bytestream for file \"%s.mid\" ", filebasename); + fprintf(outfile, "created by MIDITONES V%s on %s", VERSION, asctime(localtime(&rawtime))); +// print_command_line(argc,argv); + if (channel_mask != 0xffff) + fprintf(outfile, "// Only the masked channels were processed: %04X\n", channel_mask); + if (keyshift != 0) + fprintf(outfile, "// Keyshift was %d chromatic notes\n", keyshift); +// if (directory) { + fprintf(outfile, "const unsigned char score%d [] = {\n", file_count++); +// } else +// fprintf(outfile, "const unsigned char score [] = {\n"); + } + } + + /* process the MIDI file header */ + + hdrptr = buffer; /* pointer to file and track headers */ + process_header (); + printf (" Processing %d tracks.\n", num_tracks); + if (num_tracks > MAX_TRACKS) midi_error ("Too many tracks", buffer); + + /* initialize processing of all the tracks */ + + for (tracknum=0; tracknum < num_tracks; ++tracknum) { + start_track (tracknum); /* process the track header */ + find_note (tracknum); /* position to the first note on/off */ + /* if we are in "parse only" mode, do the whole track, + so we do them one at a time instead of time-synchronized. */ + if (parseonly) while (track[tracknum].cmd != CMD_TRACKDONE) find_note(tracknum); + } + + /* Continue processing all tracks, in an order based on the simulated time. + This is not unlike multiway merging used for tape sorting algoritms in the 50's! */ + + tracknum = 0; + if (!parseonly) do { /* while there are still track notes to process */ + struct track_status *trk; + struct tonegen_status *tg; + int tgnum; + int count_tracks; + unsigned long delta_time, delta_msec; + + /* Find the track with the earliest event time, + and output a delay command if time has advanced. + + A potential improvement: If there are multiple tracks with the same time, + first do the ones with STOPNOTE as the next command, if any. That would + help avoid running out of tone generators. In practice, though, most MIDI + files do all the STOPNOTEs first anyway, so it won't have much effect. + */ + + earliest_time = 0x7fffffff; + + /* Usually we start with the track after the one we did last time (tracknum), + so that if we run out of tone generators, we have been fair to all the tracks. + The alternate "strategy1" says we always start with track 0, which means + that we favor early tracks over later ones when there aren't enough tone generators. + */ + + count_tracks = num_tracks; + if (strategy1) tracknum = num_tracks; /* beyond the end, so we start with track 0 */ + do { + if (++tracknum >= num_tracks) tracknum=0; + trk = &track[tracknum]; + if (trk->cmd != CMD_TRACKDONE && trk->time < earliest_time) { + earliest_time = trk->time; + earliest_tracknum = tracknum; + } + } + while (--count_tracks); + + tracknum = earliest_tracknum; /* the track we picked */ + trk = &track[tracknum]; + + if (loggen) fprintf (logfile, "Earliest time is trk %d, time %ld\n", tracknum, earliest_time); + if (earliest_time < timenow) midi_error ("INTERNAL: time went backwards", trk->trkptr); + + /* If time has advanced, output a "delay" command */ + + delta_time = earliest_time - timenow; + if (delta_time) { + /* Convert ticks to milliseconds based on the current tempo */ + delta_msec = ((unsigned long) delta_time * tempo) / ticks_per_beat / 1000; + if (loggen) fprintf (logfile, "->Delay %ld msec (%ld ticks)\n", delta_msec, delta_time); + if (delta_msec > 0x7fff) { + //delta_msec = 0x3FF; + midi_error ("INTERNAL: time delta too big", trk->trkptr); + } + /* output a 15-bit delay in big-endian format */ + if (binaryoutput) { + putc ((unsigned char) (delta_msec >> 8), outfile); + putc ((unsigned char) (delta_msec & 0xff), outfile); + outfile_bytecount += 2; + } + else { + fprintf (outfile, "%ld,%ld, ", delta_msec >> 8, delta_msec & 0xff); + outfile_items(2); + } + } + timenow = earliest_time; + + /* If this track event is "set tempo", just change the global tempo. + That affects how we generate "delay" commands. */ + + if (trk->cmd == CMD_TEMPO) { + tempo = trk->tempo; + if (loggen) fprintf (logfile, "Tempo changed to %ld usec/qnote\n", tempo); + find_note (tracknum); + } + + /* If this track event is "stop note", process it and all subsequent "stop notes" for this track + that are happening at the same time. Doing so frees up as many tone generators as possible. */ + + else if (trk->cmd == CMD_STOPNOTE) do { + + // stop a note + for (tgnum=0; tgnum < num_tonegens; ++tgnum) { /* find which generator is playing it */ + tg = &tonegen[tgnum]; + if (tg->playing && tg->track == tracknum && tg->note == trk->note) { + if (loggen) fprintf (logfile, "->Stop note %02X, generator %d, track %d\n", tg->note, tgnum, tracknum); + if (binaryoutput) { + putc (CMD_STOPNOTE | tgnum, outfile); + outfile_bytecount += 1; + } + else { + fprintf (outfile, "0x%02X, ", CMD_STOPNOTE | tgnum); + outfile_items (1); + } + tg->playing = false; + trk->tonegens[tgnum] = false; + } + } + find_note (tracknum); // use up the note + } + while (trk->cmd == CMD_STOPNOTE && trk->time == timenow); + + /* If this track event is "start note", process only it. + Don't do more than one, so we allow other tracks their chance at grabbing tone generators. */ + + else if (trk->cmd == CMD_PLAYNOTE) { + bool foundgen = false; + + if (strategy2) { /* try to use the same tone generator this track used last time */ + tg = &tonegen [trk->preferred_tonegen]; + if (!tg->playing) { + tgnum = trk->preferred_tonegen; + foundgen = true; + } + } + if (!foundgen) for (tgnum=0; tgnum < num_tonegens; ++tgnum) { /* search for a free tone generator */ + tg = &tonegen[tgnum]; + if (!tg->playing) { + foundgen = true; + break; + } + } + if (foundgen) { + int shifted_note; + if (tgnum+1 > num_tonegens_used) num_tonegens_used = tgnum+1; + tg->playing = true; + tg->track = tracknum; + tg->note = trk->note; + trk->tonegens[tgnum] = true; + trk->preferred_tonegen = tgnum; + if (loggen) fprintf (logfile, "->Start note %02X, generator %d, track %d\n", trk->note, tgnum, tracknum); + shifted_note = trk->note + keyshift; + if (shifted_note < 0) shifted_note = 0; + if (shifted_note > 127) shifted_note = 127; + if (binaryoutput) { + putc (CMD_PLAYNOTE | tgnum, outfile); + putc (shifted_note, outfile); + outfile_bytecount += 2; + } + else { + fprintf (outfile, "0x%02X,%d, ", CMD_PLAYNOTE | tgnum, shifted_note); + outfile_items(2); + } + } + else { + if (loggen) fprintf (logfile, "----> No free generator, skipping note %02X, track %d\n", trk->note, tracknum); + ++notes_skipped; + } + find_note (tracknum); // use up the note + } + + } /* !parseonly do */ + while (tracks_done < num_tracks); + + if (!parseonly) { + // generate the end-of-score command and some commentary + if(binaryoutput) putc(CMD_STOP, outfile); + else { + fprintf(outfile, "5,220, "); // Závěrečná pauza 1.5 s - je to jednodušší než sahat do přehrávače. + fprintf(outfile, "0x%02x};\n// This score contains %ld bytes, and %d tone generator%s used.\n", CMD_STOP, outfile_bytecount, num_tonegens_used, num_tonegens_used == 1 ? " is" : "s are"); + if (notes_skipped) fprintf(outfile, "// %d notes had to be skipped.\n", notes_skipped); + } + printf (" %s %d tone generators were used.\n", num_tonegens_used < num_tonegens ? "Only":"All", num_tonegens_used); + if (notes_skipped) printf(" %d notes were skipped because there weren't enough tone generators.\n", notes_skipped); + printf (" %ld bytes of score data were generated.\n", outfile_bytecount); + } +} +int CreateFilename (const char * filebasename, char* filename, const char * name) { + char buf [MAXPATH]; + strncpy (buf, name, MAXPATH); + char * tmp = strstr (buf, ".mid"); + if (!tmp) return 0; + * tmp = '\0'; + strncpy (filename, filebasename, MAXPATH); + strncat (filename, buf, MAXPATH); + return 1; +} +void AppendScores (FILE * outfile, int file_count) { + fprintf (outfile, "\nconst unsigned char * const scores[] = {\n"); + int i; + for (i=0; id_name); + if (!result) continue; + printf ("Process: \"%s\"\n", filename); + ProcessMidiFile (filename); + } +/* + fprintf (outfile, "\nconst unsigned char * scores[] = {\n"); + int i; + for (i=0; i ram /* tady je řečeno, že to má být v ram */ + /DISCARD/ : { + *(.rela*) + *(.dynamic) + *(.eh*) + *(.debug*) + *(.comment*) + *(.interp) + *(.dynsym) + *(.dynstr) + *(.hash) + *(.gnu.hash) + *(.header) + } : phdr +} diff --git a/V203F6P6/midi/intelhex.cpp b/V203F6P6/midi/intelhex.cpp new file mode 100644 index 0000000..960e06e --- /dev/null +++ b/V203F6P6/midi/intelhex.cpp @@ -0,0 +1,251 @@ +#include "endien.h" +#include "intelhex.h" + +typedef __SIZE_TYPE__ size_t; +extern "C" void *memcpy (void *dest, const void *src, size_t n); +extern "C" size_t strlen (const char *s); + +static uint8_t hexval (const char c) { + if ('0' <= c && c <= '9') + return (uint8_t) c - '0'; + else if ('a' <= c && c <= 'f') + return (uint8_t) c - 'a' + 10; + else return 0; +} +static unsigned to_hex_str (char * str, const uint8_t * data, const unsigned len) { + unsigned result = 0; + const char * hexstr = "0123456789ABCDEF"; + for (unsigned n=0; n> 4]; + str [result++] = hexstr [c & 0xF]; + } + return result; +} +static uint8_t chk_sum (const uint8_t * data, const unsigned len) { + uint8_t sum = 0; + for (unsigned n=0; n> 1) + 1]; + uint8_t val = 0; + unsigned k = 0; + for (unsigned n=1; n> 12); // CS:IP ? čerti vědí, jak je to myšleno + offset = hdr.load.get(); + break; + default: + break; + } + return t; +} +bool IntelHex::CompareAckOffsset() { +//printf ("ofs: %04X=%04X\n", ackofs, offset); + if (ackofs == offset) return true; + return false; +} + +void IntelHex::getAddress (uint32_t & adr) { + adr = address; +} +void IntelHex::getLenght (uint32_t & len) { + len = total_lenght; +} +void IntelHex::getOffset (uint32_t & ofs) { + ofs = offset; +} +void IntelHex::WriteData (uint8_t * data) { + memcpy (data + offset, chunk, lenght); + offset += lenght; +} +uint8_t * IntelHex::getDataRow (uint32_t & ofs, uint32_t & len) { + ofs = offset; + len = lenght; + return chunk; +} +void IntelHex::AddOffset (uint32_t len) { + offset += len; +} + +uint32_t IntelHex::ElaRecord (char * string, const uint32_t adr) { + offset = 0; + uint32_t result = 0; + string [result++] = ':'; + UlbaAddr lba; + IhxHeader hdr; + hdr.load.set(0); + hdr.reclen = sizeof (lba); + hdr.rectyp = elaRecord; + lba.addr.set (adr >> 16); + uint8_t chk = 0; + chk += chk_sum (hdr.bytes, sizeof(IhxHeader)); + chk += chk_sum (lba.bytes, sizeof(UlbaAddr )); + chk = 0 - chk; + result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader)); + result += to_hex_str (string + result, lba.bytes, sizeof(UlbaAddr )); + result += to_hex_str (string + result, & chk, 1); + string [result++] = '\r'; + string [result++] = '\n'; + string [result] = '\0'; + return result; +} +uint32_t IntelHex::SlaRecord (char* string, const uint32_t adr) { + offset = 0; + uint32_t result = 0; + string [result++] = ':'; + EipAddr lba; + IhxHeader hdr; + hdr.load.set(0); + hdr.reclen = sizeof (lba); + hdr.rectyp = slaRecord; + lba.addr.set (adr); + uint8_t chk = 0; + chk += chk_sum (hdr.bytes, sizeof(IhxHeader)); + chk += chk_sum (lba.bytes, sizeof(EipAddr )); + chk = 0 - chk; + result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader)); + result += to_hex_str (string + result, lba.bytes, sizeof(EipAddr )); + result += to_hex_str (string + result, & chk, 1); + string [result++] = '\r'; + string [result++] = '\n'; + string [result] = '\0'; + return result; +} + +uint32_t IntelHex::DataRecord (char * string, const uint8_t * data, const uint32_t len) { + uint32_t result = 0; + string [result++] = ':'; + IhxHeader hdr; + hdr.load.set (offset); + hdr.reclen = len; + hdr.rectyp = dataRecord; + uint8_t chk = 0; + chk += chk_sum (hdr.bytes, sizeof(IhxHeader)); + chk += chk_sum (data, len); + chk = 0 - chk; + result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader)); + if (len) result += to_hex_str (string + result, data, len); + result += to_hex_str (string + result, & chk, 1); + string [result++] = '\r'; + string [result++] = '\n'; + string [result] = '\0'; + return result; +} + +uint32_t IntelHex::EofRecord (char * string) { + offset = 0; + const char * eof = ":00000001FF\r\n"; + const uint32_t result = strlen (eof); + memcpy (string, eof, result); + string [result] = '\0'; + return result; +} +uint32_t IntelHex::BTxRecord (char * string, RowTypes t) { + uint32_t result = 0; + string [result++] = ':'; + IhxHeader hdr; + hdr.load.set (offset); + hdr.reclen = 0; + hdr.rectyp = t; + uint8_t chk = 0; + chk += chk_sum (hdr.bytes, sizeof(IhxHeader)); + chk = 0 - chk; + result += to_hex_str (string + result, hdr.bytes, sizeof(IhxHeader)); + result += to_hex_str (string + result, & chk, 1); + string [result++] = '\r'; + string [result++] = '\n'; + string [result] = '\0'; + //offset += len; + return result; +} diff --git a/V203F6P6/midi/intelhex.h b/V203F6P6/midi/intelhex.h new file mode 100644 index 0000000..e469104 --- /dev/null +++ b/V203F6P6/midi/intelhex.h @@ -0,0 +1,138 @@ +#ifndef INTELHEX_H +#define INTELHEX_H +#include + +/// Maximální délka rekordu +static const unsigned MaxHexRecord = 0x80u; + +enum RowTypes : uint8_t { + // defined by Intel + dataRecord = 0, //!< datový paket + eofRecord, //!< konec dat + esaRecord, //!< X86/16.bit CS/IP počáteční adresa + ssaRecord, //!< X86/16.bit CS/IP startovací adresa + elaRecord, //!< počáteční adresa 32-bit (horních 16.bit) + slaRecord, //!< startovací adresa 32-bit + // ... defined by BT protocol + // všechny budou používat jen offset pro přenos dat. + ackRecord, //!< offset - představuje offset předchozích dat + nakRecord, //!< stejně jako předchozí + reqRecord, //!< Request for data - offset značí odkud vzhledem k počáteční adrese + ersRecord, //!< Erase - po blocích, offset je číslo bloku (závisí na typu flash) + badRecord, //!< použito pro detekci chyb +}; +/// @enum RowTypes +/// Enumerace datových typů Intel-hex formátu + +/** + * @class IntelHex + * @brief Hepler pro parsování a vytváření paketů Intel HEX formátu. + * + * Prvních 6 typů (#RowTypes) je definováno ve specifikaci např. + * zde. A protože je tam dost volného + * místa pro rozšíření, jsou zde dodefinovány další typy použité pro komunikaci. Je to tak + * jednodušší než vymýšlet nějaký vlastní formát pro potvrzování a jiné. Protokol je znakový, + * poměrně robustní, na druhou stranu má dost velku režii. + * + * Ukázalo sem že úzkým hrdlem komunikace je čekání na potvrzování, takže se režie poněkud zastírá, + * ale potvrzování být musí pokud to má trochu spolehlivě fungovat. + * */ + +class IntelHex { + public: + explicit IntelHex() noexcept; + /** + * @brief Základní funkce parseru + * + * @param data vstupní řádek (paket) + * @param len jeho délka (např. strlen(data)) + * @return RowTypes - to se pak zpracovává dál v nadřazené části např. LinkProtocol + */ + RowTypes parseRecord (const char * data, const unsigned len); + /** + * @brief Nastavení adresy + * + * Buď počáteční nebo starovací. + * + * @param adr odkaz na proměnnou, kam bude adresa nastavena + */ + void getAddress (uint32_t & adr); + /** + * @brief Nastavení délky + * @param len odkaz na proměnnou, kam bude délka nastavena + */ + void getLenght (uint32_t & len); + /** + * @brief Nastavení ofsetu + * @param ofs odkaz na proměnnou, kam bude ofset nastaven + */ + void getOffset (uint32_t & ofs); + /** + * @brief Zápis dat z příchozího dataRecord + * + * @param data kam se zapíší + */ + void WriteData (uint8_t * data); + /** + * @brief Posun ofsetu kvůli udržení konzistence + * + * @param len o kolik se má posunout + */ + void AddOffset (uint32_t len); + + /** + * @brief Vytvoření dataRecord + * + * @param string kam se record zapíše + * @param data pure data rekordu + * @param len a jejich délka + * @return uint32_t kolik bylo zapsáno do string + */ + uint32_t DataRecord (char * string, const uint8_t * data, const uint32_t len); + /** + * @brief Vytvoření elaRecord + * + * @param string kam se record zapíše + * @param adr počáteční adresa + * @return uint32_t kolik znaků bylo zapsáno do string + */ + uint32_t ElaRecord (char * string, const uint32_t adr); + /** + * @brief Vytvoření slaRecord + * + * @param string kam se record zapíše + * @param adr startovací adresa + * @return uint32_t kolik znaků bylo zapsáno do string + */ + uint32_t SlaRecord (char * string, const uint32_t adr); + /** + * @brief Vytvoření eofRecord + * + * @param string kam se record zapíše + * @return uint32_t kolik znaků bylo zapsáno do string + */ + uint32_t EofRecord (char * string); + + /** + * @brief Vytvoření BT rekordů ackRecord, nakRecord, reqRecord, ersRecord + * + * @param string kam se record zapíše + * @param t definuj typ rekordu + * @return uint32_t kolik znaků bylo zapsáno do string + */ + uint32_t BTxRecord (char * string, RowTypes t = ackRecord); + /// pomocná funkce + bool CompareAckOffsset (); + /// pomocná funkce + uint8_t * getDataRow (uint32_t & ofs, uint32_t & len); + protected: + private: + uint32_t address; + uint32_t offset; + uint32_t lenght; + uint32_t total_lenght; + uint32_t ackofs; + uint8_t chunk [MaxHexRecord]; +}; + +#endif // INTELHEX_H diff --git a/V203F6P6/midi/linkprotocol.cpp b/V203F6P6/midi/linkprotocol.cpp new file mode 100644 index 0000000..700076d --- /dev/null +++ b/V203F6P6/midi/linkprotocol.cpp @@ -0,0 +1,76 @@ +#include "helpers.h" +#include "linkprotocol.h" + +#define trace(...) +typedef __SIZE_TYPE__ size_t; +extern "C" size_t strlen (const char *s); + +LinkProtocol::LinkProtocol(CdcCmd & g, MemoryBase & b) noexcept : cdccmd(g), ihx(), driver(b), + begin_addr(0), start_addr(0), lenght (0) { +} +void LinkProtocol::ParseLine (const char * line) { + const unsigned readen = strlen (line); + RowTypes t = ihx.parseRecord (line, readen); + switch (t) { + case dataRecord: AcquireDataRecord (); break; + case elaRecord: AcquireElaRecord (); break; + case slaRecord: AcquireSlaRecord (); break; + case eofRecord: AcquireEofRecord (); break; + case reqRecord: AcquireReqRecord (); break; + case ersRecord: AcquireErsRecord (); break; + default: + trace ("BAD record 0x%02X\r\n", t); + break; + } + +} +void LinkProtocol::AcquireDataRecord() { + uint32_t ofs, len; + uint8_t * ptr = ihx.getDataRow (ofs, len); + uint32_t res = driver.Write (begin_addr + ofs, ptr, len); + if (res) { + SendAck(); + ihx.AddOffset (res); // posun offsetu az po ack + return; + } + SendAck (false); +} +// První paket, který musí přijít při každé akci. +void LinkProtocol::AcquireElaRecord() { + ihx.getAddress (begin_addr); + trace("Begin = 0x%08X\r\n", begin_addr); + SendAck(); +} +typedef void (*pHandler) (void); +void LinkProtocol::AcquireSlaRecord() { + ihx.getAddress (start_addr); + trace("Start = 0x%08X\r\n", start_addr); +} +void LinkProtocol::AcquireEofRecord() { + ihx.getLenght (lenght); + trace ("Lenght = %d\r\n", lenght); + SendAck(); +} +void LinkProtocol::AcquireReqRecord() { + static constexpr unsigned chunk_size = 0x20; + uint8_t data [chunk_size]; + uint32_t res, len, ofs; + len = chunk_size; + ihx.getOffset (ofs); + //trace ("AcquireReqRecord: %04X, len=%d\r\n", ofs, len); + res = driver.Read (begin_addr + ofs, data, len); + res = ihx.DataRecord(strbuf, data, res); + cdccmd.SendString (strbuf, res); +} +void LinkProtocol::AcquireErsRecord() { + uint32_t block; + ihx.getOffset (block); + driver.Erase (block); + SendAck(); +} + +void LinkProtocol::SendAck (bool ok) { + RowTypes t = ok ? ackRecord : nakRecord; + const uint32_t r = ihx.BTxRecord (strbuf, t); + cdccmd.SendString (strbuf, r); +} diff --git a/V203F6P6/midi/linkprotocol.h b/V203F6P6/midi/linkprotocol.h new file mode 100644 index 0000000..89bc4c1 --- /dev/null +++ b/V203F6P6/midi/linkprotocol.h @@ -0,0 +1,55 @@ +#ifndef LINKPROTOCOL_H +#define LINKPROTOCOL_H + +#include +#include "intelhex.h" + +class CdcCmd; +class MemoryBase; +/** + * @class LinkProtocol + * @brief Protokol po CDC. + * + * Vychází z formátu IntelHex. komunikaci začíná PC, modul odpovídá. První co musí PC (vždy na začátku konunikace !) poslat + * je paket ElaRecord (4), který určuje počáteční adresu - podle ní modul vybere potřebný ovladač paměti. Formy komunikace : + * -# Mazání obsahu flash. Probíhá po jednotlivých blocích. + * - PC -> MODUL : ersRecord (v poli offset číslo bloku relativně k počátku typu paměti) + * - MODUL -> PC : ackRecord (v poli offset číslo bloku relativně k počátku typu paměti) + * -# Zápis dat do paměti. + * - PC -> MODUL : dataRecord (max. 32 bytů) + * - MODUL -> PC : ackRecord nebo nakRecord pokud je cosi špatně + * -# Čtení dat z paměti. + * - PC -> MODUL : reqRecord (v poli offset odkud chci číst) + * - MODUL -> PC : dataRecord - pokud má nulovou délku, další data nejsou. Posílána je délka 32 bytů. + * -# Spuštění uživatelského programu. + * - PC -> MODUL : slaRecord + * - bez odpovědi - je to jen pro uživatelské testy a může to špatně skončit. Vlastně je to přidáno + * jen proto, že to jde. + * */ +class LinkProtocol { + static constexpr unsigned strbuflen = 0x80u; + public: + explicit LinkProtocol(CdcCmd & g, MemoryBase & b) noexcept; + void ParseLine (const char * line); + protected: + void AcquireDataRecord (); + void AcquireElaRecord (); + void AcquireSlaRecord (); + void AcquireEofRecord (); + void AcquireReqRecord (); + void AcquireErsRecord (); + + void SendAck (bool ok=true); + private: + CdcCmd & cdccmd; + IntelHex ihx; + MemoryBase & driver; + + uint32_t begin_addr; + uint32_t start_addr; + uint32_t lenght; + + char strbuf [strbuflen]; +}; + +#endif // LINKPROTOCOL_H diff --git a/V203F6P6/midi/main.cpp b/V203F6P6/midi/main.cpp new file mode 100644 index 0000000..ab7c0d5 --- /dev/null +++ b/V203F6P6/midi/main.cpp @@ -0,0 +1,65 @@ +#include "system.h" +#include "midiplayer.h" +#include "cdc_class.h" +#include "linkprotocol.h" +#include "helpers.h" +/* + * Player na tomto čipu má 12 generátorů, lepší + * rozlišení PWM, tedy lepší zvuk. + * Zase má menší paměť, takže stálo za zkoušku dát + * data melodií do externí SPI flash. Číst to z ní + * po bytu by asi bylo dost neefektivní, takže je tam + * vložena třída Cache. Zároveň to ukazuje jak efektivní + * je použití C++ proti čistému C. Zatímco v C-čku by to + * asi byl velký zásah do původního kódu, zde stačí + * ve třídě Cache přetížit 3 operátory a do původního + * kódu se nemusí takřka zasahovat. + * Binární obraz melodií pro externí flash se vytvoří + * pomocí Makefile v adresáří img, je nezávislý na + * firmware procesoru. Melodie to bere z adresáře mid, + * pořadí je víceméně náhodné. + * + * Vzhledem k tomu, že je tam místa dost, vejde se tam + * i programátor nor flash, takže není potřeba přepínat + * firmware, stačí připojit do USB a pokud se to do 2s + * enumeruje, můžeme programovat. + */ +extern "C" volatile uint32_t bDeviceState; +GpioClass led (GPIOB,8u); +// player +static MidiPlayer player (led); +static PwmClass pcm; +// programátor +static cdc_class cdc; +static MemoryBase mem; +static CdcCmd cmd; +static LinkProtocol lnk (cmd, mem); + +static void player_loop () { + for (;;) { + player.pass(); + } +} +static void programmer_loop () { + led << true; + unsigned command = 0u; + for (;;) { + char * res = cmd.GetLine(command); + if (command == 0u) continue; + lnk.ParseLine(res); + } +} + +int main (void) { + delay_init (); + cdc.init (); + Delay_Ms (2000); // čekat se musí poměrně dlouho + if (bDeviceState > 1u) { // enumerováno, můžeme programovat + cmd += cdc; + programmer_loop(); + } else { // jinak můžeme hrát + pcm.attach (player); + player_loop (); + } + return 0; +} diff --git a/V203F6P6/midi/mid b/V203F6P6/midi/mid new file mode 120000 index 0000000..9fba256 --- /dev/null +++ b/V203F6P6/midi/mid @@ -0,0 +1 @@ +../../V203/midi/mid \ No newline at end of file diff --git a/V203F6P6/midi/midiplayer.cpp b/V203F6P6/midi/midiplayer.cpp new file mode 100644 index 0000000..6ec2441 --- /dev/null +++ b/V203F6P6/midi/midiplayer.cpp @@ -0,0 +1,98 @@ +#include "midiplayer.h" +/** + * @file + * @brief Jednoduchý přehrávač midi souborů. + * + * Kompletní midi obsahuje zvukové fonty, které jsou obrovské. Tohle je velice zjednodušené, + * takže docela dobře přehrává skladby typu ragtime, orchestrální midi jsou skoro nepoužitelné. + * Přesto se to pro jednoduché zvuky může hodit, protože je to poměrně nenáročné na systémové + * prostředky. Může to fungovat dokonce i na 8-bitovém uP. + * */ + +static constexpr unsigned AudioMidiDelay = 24; +static constexpr int INPUT_BIT_RANGE = 16; +static constexpr unsigned SIGMA_MASK = (1u << (INPUT_BIT_RANGE + 0)) - 1u; +static constexpr unsigned SIGNED_OFFEST = (1u << (INPUT_BIT_RANGE - 1)); + // Předpokládá se na vstupu signed int o šířce INPUT_BIT_RANGE + // přičemž 0 na vstupu odpovídá MAXPWM / 2 na výstupu. Vypadá to divně, ale funguje. +static unsigned pwm_sd (const int input) { + static unsigned sigma = 0; // podstatné je, že proměnná je statická + const unsigned sample = (input + SIGNED_OFFEST) * MAXPWM; + sigma &= SIGMA_MASK; // v podstatě se odečte hodnota PWM + sigma += sample; // integrace prostým součtem + return sigma >> INPUT_BIT_RANGE; +} + +/******************************************************************/ +/// Konstruktor +MidiPlayer::MidiPlayer(GpioClass & io) noexcept : OneWay(), led (io), passcnt (0u), scores(), melody() { + pause = 0; + scores = 0; + melody = * scores++; + running = false; +} +/// Počítá další vzorek +short MidiPlayer::nextSample (void) { + if (pause) pause -= 1; // Časování tónu + else ToneChange(); // Nový tón - MidiPlayer::ToneChange + return genSample (); +} +void MidiPlayer::pass() { + const bool b = passcnt & 0x100000; + led << b; + passcnt += 1u; +} + +unsigned MidiPlayer::Send (uint16_t * const ptr, const unsigned len) { + const bool b = false; // but; + if (!b and !running) running = true; + + if (!running) { + for (unsigned n=0; n> 1; + return len; + } + + for (unsigned n=0; n>= 4; + switch (cmd) { + case 0x8: // off + gens[geno].setMidiOff(); + break; + case 0x9: // on + midt = * melody++; + gens[geno].setMidiOn (midt); + break; + default: + stop(); + return; // melodie končí eventem 0xf0 + } + } else { // pause + midt = * melody++; + // Když to trochu uteče, zase se z toho nestřílí, tak to nechme být. + pause = ((unsigned int) cmd << 8) + midt; // v ms + pause *= AudioMidiDelay; // ale máme vzorkování cca 24 kHz + return; + } + } +} diff --git a/V203F6P6/midi/midiplayer.h b/V203F6P6/midi/midiplayer.h new file mode 100644 index 0000000..ea76c3a --- /dev/null +++ b/V203F6P6/midi/midiplayer.h @@ -0,0 +1,45 @@ +#ifndef DACPLAYER_H +#define DACPLAYER_H +#include "oneway.h" +#include "gpio.h" +#include "tone.h" +#include "audio.h" +#include "pwmclass.h" +#include "cache.h" + +/// Třída, která hraje čistě na pozadí. +class MidiPlayer : public OneWay { + // Veřejné metody + public: + /// Konstruktor + explicit MidiPlayer (GpioClass & io) noexcept; + unsigned Send (uint16_t * const ptr, const unsigned len) override; + void stop (); + void pass (); + protected: + // Chráněné metody + /// Obsluha tónu + void ToneChange (void); + /// Obsluha vzorku + short nextSample (void); + /// Generuj vzorek pro všechny tóny @return Vzorek + short genSample (void) { + int res = 0; + for (unsigned int i=0; i maxValue) res = maxValue; + if (res < minValue) res = minValue; + return (res); + } + private: + GpioClass & led; + Tone gens[maxGens]; /// Generátory tónů + unsigned passcnt; + volatile bool running; + Cache scores; + Cache melody; + volatile int pause; + +}; + +#endif // DACPLAYER_H diff --git a/V203F6P6/midi/norflash.cpp b/V203F6P6/midi/norflash.cpp new file mode 100644 index 0000000..a9d7b19 --- /dev/null +++ b/V203F6P6/midi/norflash.cpp @@ -0,0 +1,82 @@ +//#include "string.h" +#include "norflash.h" +extern "C" { + extern void delay_init (); + extern void Delay_Ms (const unsigned dly); +}; +union FlashCommandHeader { + struct { + FLASH_COMMANDS cmd : 8; + uint32_t adr : 24; // adresa je BIG ENDIEN - vyšší byte vystupuje po SPI dříve (MSB FIRST). + }__attribute__((packed)); + uint8_t bytes [4]; +}__attribute__((packed)); +static inline uint32_t be_set_24 (const uint32_t p) { + return ((p & 0x000000ff) << 16) + | ((p & 0x0000ff00) << 0) + | ((p & 0x00ff0000) >> 16); +} +static constexpr unsigned FlashBlockLen = 0x1000u; // 4KiB = 4096 B + +NorFlash::NorFlash() noexcept : spi() { + delay_init(); +} +unsigned int NorFlash::ReadBlock(const unsigned int addr, uint8_t * data, const unsigned int len) { + FlashCommandHeader header; + header.cmd = FLASH_4READ; + header.adr = be_set_24(addr); + spi.ChipSelect(true); + for (unsigned n=0u; n +#include "spiblocked.h" + /// Enumerace povelů pro SPI FLASH. + enum FLASH_COMMANDS : uint8_t { + /* single byte commands */ + FLASH_WRDI = 0x04, // nepoužito + FLASH_WREN = 0x06, + FLASH_RDID = 0x9F, + FLASHRSTEN = 0x66, // nepoužito + FLASH__RST = 0x99, // nepoužito + FLASH__RES = 0xAB, // release from power down, nepoužito + FLASH__DPD = 0xB9, // power down, nepoužito + // multi - byte + FLASH_RDSR1 = 0x05, + // dále se mohou povely lišit + FLASH_RDSR2 = 0x35, // nepoužito + FLASH_4READ = 0x03, + FLASH_4PP = 0x02, + FLASH_4SE = 0x20, + }; + +class NorFlash { + SpiBlocked spi; + public: + explicit NorFlash () noexcept; + unsigned ReadBlock (const unsigned addr, uint8_t * data, const unsigned len); + unsigned WriteBlock (const unsigned addr, const uint8_t * data, const unsigned len); + bool EraseSector (const unsigned addr); + protected: + /// Jednoduchý příkaz + bool SingleCommand (const FLASH_COMMANDS cmd) const; + /// Načtení registru STATUS1 nebo STATUS2 pomocí FLASH_RDSRx + uint8_t ReadStatus (const FLASH_COMMANDS cmd) const; + /// Čekání na dokončení operace zápisu / mazání + bool WaitForReady (const unsigned ms = 5u) const; +}; + +#endif // NORFLASH_H diff --git a/V203F6P6/midi/pwmclass.cpp b/V203F6P6/midi/pwmclass.cpp new file mode 100644 index 0000000..b4a757d --- /dev/null +++ b/V203F6P6/midi/pwmclass.cpp @@ -0,0 +1,110 @@ +#include "pwmclass.h" +#include "gpio.h" +typedef __SIZE_TYPE__ size_t; +extern "C" { + [[gnu::interrupt]] extern void DMA1_Channel2_IRQHandler( void ); +}; +static PwmClass * pPwmInstance = nullptr; +void DMA1_Channel2_IRQHandler( void ) { + DMA1_Type::INTFR_DEF state (DMA1.INTFR); + if (state.B.GIF2 != RESET) { + DMA1.INTFCR.B.CGIF2 = SET; + } else return; + if (state.B.HTIF2 != RESET) { + DMA1.INTFCR.B.CHTIF2 = SET; + if (pPwmInstance) pPwmInstance->send(false); + } + if (state.B.TCIF2 != RESET) { + DMA1.INTFCR.B.CTCIF2 = SET; + if (pPwmInstance) pPwmInstance->send(true); + } +} +/* + * initialize TIM2 for PWM + */ +inline void PwmClass::TimInit() noexcept { + // Enable GPIOA and TIM1 + RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto { + r.B.IOPAEN = SET; + r.B.IOPBEN = SET; + //r.B.AFIOEN = SET; + return r.R; + }); + RCC.APB1PCENR.B.TIM2EN = SET; + // PA2 is TIM2_CH3, 10MHz Output alt func, push-pull + GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto { + r.B.CNF2 = 2u; + r.B.MODE2 = 1u; + return r.R; + }); + // PB1 is DEN, active H Output 10 MHz, push-pull + GPIOB.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto { + r.B.CNF1 = 0u; + r.B.MODE1 = 1u; + return r.R; + }); + GPIOB.BSHR.B.BS1 = SET; // set to H + // Reset TIM2 to init all regs + RCC.APB1PRSTR.B.TIM2RST = SET; + RCC.APB1PRSTR.B.TIM2RST = RESET; + // CTLR1: default is up, events generated, edge align + // SMCFGR: default clk input is CK_INT + // Prescaler + TIM2.PSC.R = 0u; // 144 MHz + // Auto Reload - sets period + TIM2.ATRLR.R = MAXPWM - 1; // 24 kHz + + // CH3 Mode is output, PWM1 (CC3S = 00, OC3M = 110) + TIM2.CHCTLR2_Output.modify([](TIM2_Type::CHCTLR2_Output_DEF & r) -> auto { + r.B.OC3M = 0x6u; + return r.R; + }); + // Enable TIM1 outputs + TIM2.CCER.modify([](TIM2_Type::CCER_DEF & r) -> auto { + // Enable CH3, CH3 output, positive pol + r.B.CC3E = SET; + //r.B.CC3P = SET; // negative + return r.R; + }); + // Reload immediately + Trigger DMA + TIM2.SWEVGR.B.UG = SET; + TIM2.DMAINTENR.B.UDE = SET; +} +inline void PwmClass::DmaInit() noexcept { + // Enable DMA + RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto { + r.B.SRAMEN = SET; + r.B.DMA1EN = SET; + return r.R; + }); + // DMA can be configured to attach to T2UP + // The system can only DMA out at ~2.2MSPS. 2MHz is stable. + DMA1.CNTR2 .R = FULL_LEN; + DMA1.MADDR2.R = reinterpret_cast(buffer); + DMA1.PADDR2.R = reinterpret_cast(& TIM2.CH3CVR); + DMA1.CFGR2.modify([](DMA1_Type::CFGR2_DEF & r) -> auto { + r.B.DIR = SET; // MEM2PERIPHERAL + r.B.PL = 2u; // High priority. + r.B.PSIZE = 1u; // 16-bit peripheral + r.B.MSIZE = 1u; // 16-bit memory + r.B.MINC = SET; // Increase memory. + r.B.CIRC = SET; // Circular mode. + r.B.HTIE = SET; // Half-trigger + r.B.TCIE = SET; // Whole-trigger + // Enable DMA1 CH2 + r.B.EN = SET; + return r.R; + }); +} + +PwmClass::PwmClass() noexcept : count(0u), pL(buffer), pH(buffer + HALF_LEN), src(nullptr) { + pPwmInstance = this; + TimInit (); + DmaInit (); + // Enable TIM2 +} +void PwmClass::attach(OneWay& s) { + NVIC.EnableIRQ (DMA1_Channel2_IRQn); + TIM2.CTLR1.B.CEN = SET; + src = & s; +} diff --git a/V203F6P6/midi/pwmclass.h b/V203F6P6/midi/pwmclass.h new file mode 100644 index 0000000..412d4e8 --- /dev/null +++ b/V203F6P6/midi/pwmclass.h @@ -0,0 +1,35 @@ +#ifndef PWMCLASS_H +#define PWMCLASS_H +#include "system.h" +#include "oneway.h" + static constexpr unsigned MAXPWM = 6000u; +/* Používá TIM2, PWM kanál 3, DMA1 kanál 2, přerušení DMA1_Channel2_IRQHandler */ +class PwmClass { + static constexpr unsigned HALF_LEN = 0x80u; + static constexpr unsigned FULL_LEN = 2u * HALF_LEN; + volatile unsigned count; + uint16_t * const pL; + uint16_t * const pH; + uint16_t buffer [FULL_LEN]; + OneWay * src; + public: + explicit PwmClass () noexcept; + void attach (OneWay & s); + void send (const bool b) { + if (!src) return; + if (b) src->Send (pH, HALF_LEN); + else src->Send (pL, HALF_LEN); + if (count) count -= 1u; + } + void delay (const unsigned frames = 50u) { + count = frames; + while (count) { + asm volatile ("nop"); + } + } + protected: + void DmaInit () noexcept; + void TimInit () noexcept; +}; + +#endif // PWMCLASS_H diff --git a/V203F6P6/midi/spiblocked.cpp b/V203F6P6/midi/spiblocked.cpp new file mode 100644 index 0000000..0d48b88 --- /dev/null +++ b/V203F6P6/midi/spiblocked.cpp @@ -0,0 +1,66 @@ +#include "system.h" +#include "spiblocked.h" +enum SPICLK : uint32_t { + FPCLK_2 = 0u, // 72 MHz + FPCLK_4, // 36 MHz + FPCLK_8, // 18 MHz + FPCLK_16, // 9 MHz + FPCLK_32, // 4.5 MHz + FPCLK_64, // 2.25 MHz + FPCLK_128, // 1.125 MHz + FPCLK_256, // 0.5625 MHz +}; +static constexpr unsigned FM = 3u; // 50 MHz +static void InitPins () noexcept { + // PA4 - NSS, PA5 - SCK, PA6 - MISO, PA7 - MOSI + GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> uint32_t { + r.B.MODE4 = FM; + r.B.CNF4 = 0u; // gen push - pull + r.B.MODE5 = FM; + r.B.CNF5 = 2u; // alt push - pull + r.B.MODE6 = 0u; // input mode + r.B.CNF6 = 1u; // floating + r.B.MODE7 = FM; + r.B.CNF7 = 2u; // alt push - pull + return r.R; + }); + // AFIO - default + GPIOA.BSHR.B.BS4 = SET; +} + +SpiBlocked::SpiBlocked() noexcept { + RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> uint32_t { + r.B.SPI1EN = SET; + r.B.IOPAEN = SET; + //r.B.AFIOEN = SET; + return r.R; + }); + InitPins(); + RCC.APB2PRSTR.B.SPI1RST = SET; + RCC.APB2PRSTR.B.SPI1RST = RESET; + SPI1.CTLR1.modify([](SPI1_Type::CTLR1_DEF & r) -> uint32_t { + r.B.CPHA = RESET; + r.B.CPOL = RESET; + r.B.MSTR = SET; // master + r.B.DFF = RESET; // 8 bit + r.B.SSM = SET; // software + r.B.SSI = SET; // !!! netuším proč, ale jinak se nenastaví MSTR a SPE + r.B.LSBFIRST = RESET; + r.B.BR = FPCLK_64; + return r.R; + }); + SPI1.CRCR.R = 7u; + SPI1.CTLR1.B.SPE = SET; +} +uint8_t SpiBlocked::ReadWriteByte(const uint8_t data) const { + while (SPI1.STATR.B.TXE == RESET); + SPI1.DATAR.B.DATAR = data; + while (SPI1.STATR.B.RXNE == RESET); + return SPI1.DATAR.B.DATAR; +} +void SpiBlocked::ChipSelect(const bool on) const { + if (on) GPIOA.BSHR.B.BR4 = SET; + else GPIOA.BSHR.B.BS4 = SET; +} + + diff --git a/V203F6P6/midi/spiblocked.h b/V203F6P6/midi/spiblocked.h new file mode 100644 index 0000000..852c818 --- /dev/null +++ b/V203F6P6/midi/spiblocked.h @@ -0,0 +1,12 @@ +#ifndef SPIBLOCKED_H +#define SPIBLOCKED_H +#include + +class SpiBlocked { +public: + explicit SpiBlocked () noexcept; + uint8_t ReadWriteByte (const uint8_t data) const; + void ChipSelect (const bool on) const; +}; + +#endif // SPIBLOCKED_H diff --git a/V203F6P6/midi/ton/gen.cpp b/V203F6P6/midi/ton/gen.cpp new file mode 100644 index 0000000..0cc1961 --- /dev/null +++ b/V203F6P6/midi/ton/gen.cpp @@ -0,0 +1,76 @@ +#include +#include +#include "../audio.h" + +static constexpr int maxTone = (1L<<23) - 1; + +int limit (double tone) { + int k = (int) round (tone); + if (k > maxTone) k = 0; + return k; +} +int normalize (double val, double scale) { + return (int) round (val * scale); +} +int main (void) { + double base, dint; + int i,j; + + base = 8.1757989156; // C5 v Hz (http://www.tonalsoft.com/pub/news/pitch-bend.aspx) + base *= (double)(1UL << 24) / double (AudioSampleRate); + dint = pow(2.0, 1.0 / 12.0); + + FILE* out = fopen ("miditone.c","w"); + // Tabulka inkrementů pro midi tóny + fprintf (out, "const unsigned int midiTones[] = {\n"); + for (i=0,j=0; i<127; i++) { + fprintf (out, "%8d, ", limit (base)); + if (++j >= 12) { + j = 0; + fprintf (out, "\n"); + } + base *= dint; + } + fprintf (out, "%8d };\n\n", limit (base)); + // Vzorky pro jednu periodu tónu včetně barvy + double samples [256], max = 0.0, val; + base = M_PI / 128.0; + for (i=0; i<256; i++) { + val = 0.0; + val += 1.0 * sin (1.0 * base * (double) i); + // Je dobré přidat nějaké harmonické, jinak je tón chudý + val += 0.3 * sin (2.0 * base * (double) i); + // 7. harmonická je nepříjemná, zkuste si to. + // val += 0.5 * sin (7.0 * base * (double) i); + if (val > +max) max = +val; + if (val < -max) max = -val; + samples [i] = val; + } + max = (double)(0x1FFF) / max; // normála do 14. bitů + // mormalizace a výpis + fprintf (out, "const short onePeriod[] = {\n"); + for (i=0,j=0; i<255; i++) { + fprintf (out, "%6d, ", normalize (samples[i], max)); + if (++j >= 8) { + j = 0; + fprintf (out, "\n"); + } + base *= dint; + } + fprintf (out, "%6d };\n\n", normalize (samples[i], max)); + + unsigned Attack = maxAmplt; + fprintf (out, "const unsigned attackTable[] = {\n"); + for (i=0,j=0; i<127; i++) { + fprintf (out, "0x%08X, ", Attack); + if (++j >= 8) { + j = 0; + fprintf (out, "\n"); + } + Attack -= Attack / 20; + } + fprintf (out, "0x%08X };\n\n", Attack); + + + fclose (out); +} diff --git a/V203F6P6/midi/tone.cpp b/V203F6P6/midi/tone.cpp new file mode 100644 index 0000000..14955fe --- /dev/null +++ b/V203F6P6/midi/tone.cpp @@ -0,0 +1,69 @@ +#include "tone.h" +/** + * Přidán attack - zmizí rušivé lupání, prodlouží se obsluha tónu. + * */ + +extern "C" const short onePeriod[]; +extern "C" const unsigned int midiTones[]; +extern "C" const unsigned int attackTable[]; + +static constexpr unsigned defFall = 16u; +static constexpr unsigned maxAttack = 127u; + +Tone::Tone() noexcept { + ampl = 0; freq = 0; base = 0; atck = 0; + fall = defFall; +} + +void Tone::setAmpl (unsigned int a) { + ampl = a; +} + +void Tone::setFreq (unsigned int f) { + freq = f; +} + +void Tone::setMidiOn (unsigned int m) { + freq = midiTones [m & 0x7F]; + if (freq) atck = maxAttack; + fall = 1; +} + +void Tone::setMidiOff (void) { + fall = defFall; + /* + base = 0; + freq = 0; + */ +} + +void Tone::setFall (unsigned int f) { + fall = f; +} + +int Tone::step (void) { + unsigned int k,t; + int y; + // Spočteme index x pro přístup do tabulky + const unsigned x = (base >> 16) & 0xFF; + y = onePeriod [x]; // vzorek vezmeme z tabulky + + // k je horní půlka amplitudy + k = ampl >> 16; + y *= k; // vzorek násobíme amplitudou (tedy tím vrškem) + y >>= 12; // a vezmeme jen to, co potřebuje DAC + k *= fall; // Konstanta fall určuje rychlost poklesu amplitudy, + // čím více, tím je rychlejší. Pokud by bylo 1, pokles je 2^16 vzorků, což už je pomalé. + base += freq; // časová základna pro další vzorek + + if (atck) { // přidán attack = náběh amplitudy + t = attackTable [atck]; // z tabulky + if (t > ampl) ampl = t; // prevence lupání - nemí být skok amplitudy + atck -= 1; // dočasovat k nule + } else + ampl -= k; // exponenciální pokles amplitudy + // a je to + + return y; +} + diff --git a/V203F6P6/midi/tone.h b/V203F6P6/midi/tone.h new file mode 100644 index 0000000..cd0545d --- /dev/null +++ b/V203F6P6/midi/tone.h @@ -0,0 +1,26 @@ +#ifndef TONE_H +#define TONE_H + +class Tone { + public: + explicit Tone () noexcept; + void setMidiOn (unsigned int m); + void setMidiOff (void); + void setFreq (unsigned int f); + void setAmpl (unsigned int a); + void setFall (unsigned int f); + int step (void); + private: + /// Amplituda tónu, interní proměnná + unsigned int ampl; + /// Exponenciální doběh - čím víc, tím rychlejší (0 = stálý) + unsigned int fall; + /// Frekvence (normalizovaná) + unsigned int freq; + /// Přetékající index do tabulky vzorků + unsigned int base; + /// Attack = index do tabulky attackTable + unsigned int atck; +}; + +#endif // TONE_H diff --git a/V203F6P6/midi/usb_desc.cpp b/V203F6P6/midi/usb_desc.cpp new file mode 100644 index 0000000..76dcebb --- /dev/null +++ b/V203F6P6/midi/usb_desc.cpp @@ -0,0 +1,80 @@ +/********************************** (C) COPYRIGHT ******************************* + * File Name : usb_desc.c + * Author : WCH + * Version : V1.0.0 + * Date : 2022/08/20 + * Description : usb device descriptor,configuration descriptor, + * string descriptors and other descriptors. +********************************************************************************* +* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. +* Attention: This software (modified or not) and binary are used for +* microcontroller manufactured by Nanjing Qinheng Microelectronics. +*******************************************************************************/ + +#include "usb_desc.h" + +/* Device Descriptor */ +const uint8_t USBD_DeviceDescriptor[] = +{ + 0x12, // bLength + 0x01, // bDescriptorType (Device) + 0x10, 0x01, // bcdUSB 1.10 + 0x02, // bDeviceClass + 0x00, // bDeviceSubClass + 0x00, // bDeviceProtocol + DEF_USBD_UEP0_SIZE, // bMaxPacketSize0 64 + (uint8_t)DEF_USB_VID, (uint8_t)(DEF_USB_VID >> 8), // idVendor 0x1A86 + (uint8_t)DEF_USB_PID, (uint8_t)(DEF_USB_PID >> 8), // idProduct 0x5537 + DEF_IC_PRG_VER, 0x00, // bcdDevice 0.01 + 0x01, // iManufacturer (String Index) + 0x02, // iProduct (String Index) + 0x03, // iSerialNumber (String Index) + 0x01, // bNumConfigurations 1 +}; + +/* Configuration Descriptor */ +const uint8_t USBD_ConfigDescriptor[] = +{ + /* Configure descriptor */ + 0x09, 0x02, 0x43, 0x00, 0x02, 0x01, 0x00, 0x80, 0x32, + + /* Interface 0 (CDC) descriptor */ + 0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00, + + /* Functional Descriptors */ + 0x05, 0x24, 0x00, 0x10, 0x01, + + /* Length/management descriptor (data class interface 1) */ + 0x05, 0x24, 0x01, 0x00, 0x01, + 0x04, 0x24, 0x02, 0x02, + 0x05, 0x24, 0x06, 0x00, 0x01, + + /* Interrupt upload endpoint descriptor */ + 0x07, 0x05, 0x81, 0x03, (uint8_t)DEF_USBD_ENDP1_SIZE, (uint8_t)( DEF_USBD_ENDP1_SIZE >> 8 ), 0x01, + + /* Interface 1 (data interface) descriptor */ + 0x09, 0x04, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00, + + /* Endpoint descriptor */ + 0x07, 0x05, 0x02, 0x02, (uint8_t)DEF_USBD_ENDP2_SIZE, (uint8_t)( DEF_USBD_ENDP2_SIZE >> 8 ), 0x00, + + /* Endpoint descriptor */ + 0x07, 0x05, 0x83, 0x02, (uint8_t)DEF_USBD_ENDP3_SIZE, (uint8_t)( DEF_USBD_ENDP3_SIZE >> 8 ), 0x00, +}; +#define DEF_STRDESC(p,n) w_text<(sizeof(p)>>1)>n={sizeof(n)-2u,3u,{p}} +template struct w_text { + uint8_t len, typ; + const char16_t str [N]; +}; +static const DEF_STRDESC((u"Kizarm Labs."), str_1); +static const DEF_STRDESC((u"Flash Programmer"),str_2); +static const DEF_STRDESC((u"0001"), str_3); +/* Language Descriptor */ +static const uint8_t LangDescr[] = { + 0x04, 0x03, 0x09, 0x04 +}; +const uint8_t * USBD_StringLangID = reinterpret_cast(LangDescr); +const uint8_t * USBD_StringVendor = reinterpret_cast(&str_1); +const uint8_t * USBD_StringProduct = reinterpret_cast(&str_2); +const uint8_t * USBD_StringSerial = reinterpret_cast(&str_3); +