add midi
This commit is contained in:
parent
7ed58d1151
commit
9255d8d7e1
20 changed files with 1665 additions and 0 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -7,3 +7,8 @@
|
||||||
*.bin
|
*.bin
|
||||||
*.map
|
*.map
|
||||||
*.elf
|
*.elf
|
||||||
|
midi/melody.c
|
||||||
|
midi/miditone.c
|
||||||
|
midi/ton/gen
|
||||||
|
midi/ton/miditones
|
||||||
|
|
||||||
|
|
|
@ -36,3 +36,11 @@ Tvar výpisů je tento:<br />
|
||||||
A--3-B--34---- DP: --VV Z-V- -V-V ----<br />
|
A--3-B--34---- DP: --VV Z-V- -V-V ----<br />
|
||||||
A1---B-------8 DP: VVVZ VZVV ZZZV -VZV<br />
|
A1---B-------8 DP: VVVZ VZVV ZZZV -VZV<br />
|
||||||
A1---B---45678 DP: ---Z -Z-V Z-Z- -VZV<br />
|
A1---B---45678 DP: ---Z -Z-V Z-Z- -VZV<br />
|
||||||
|
|
||||||
|
## midi
|
||||||
|
Funguje to podobně jako na STM32F051, jen to má omezení dané asi především
|
||||||
|
tím, že tohle nemá hardwarovou násobičku. Tedy jen 4 generátory. Na hračky
|
||||||
|
typu melodický zvonek, piánko atd. to stačí. Výhoda je, že piny vydrží 20mA,
|
||||||
|
je možné připojit 30 Ohm sluchátka (do série) plus sériový odpor 100 Ohm mezi
|
||||||
|
piny PD0 a PD2 a hraje to s dostatečnou hlasitostí a docela čistě.
|
||||||
|
|
||||||
|
|
117
ch32v003/pcmdma.cpp
Normal file
117
ch32v003/pcmdma.cpp
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
#include "system.h"
|
||||||
|
#include "pcmdma.h"
|
||||||
|
#include "gpio.h"
|
||||||
|
|
||||||
|
static PcmDma * pInstance = nullptr;
|
||||||
|
extern "C" void DMA1_Channel5_IRQHandler( void ) __attribute__((interrupt));
|
||||||
|
void DMA1_Channel5_IRQHandler( void ) {
|
||||||
|
DMA1_Type::INTFR_DEF state (DMA1.INTFR);
|
||||||
|
DMA1.INTFCR.R = state.R; // clear all
|
||||||
|
if (!pInstance) return;
|
||||||
|
if (state.B.HTIF5 != RESET) pInstance->send(false);
|
||||||
|
else if (state.B.TCIF5 != RESET) pInstance->send(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* initialize TIM1 for PWM
|
||||||
|
*/
|
||||||
|
static inline void tim1pwm_init () noexcept {
|
||||||
|
// Enable GPIOD and TIM1
|
||||||
|
RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto {
|
||||||
|
r.B.IOPDEN = SET;
|
||||||
|
//r.B.IOPCEN = SET;
|
||||||
|
r.B.TIM1EN = SET;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
|
||||||
|
// PD0 is T1CH1N, PD2 is T1CH1, 10MHz Output alt func, push-pull
|
||||||
|
GPIOD.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
|
||||||
|
r.B.CNF0 = 2u;
|
||||||
|
r.B.MODE0 = 1u;
|
||||||
|
r.B.CNF2 = 2u;
|
||||||
|
r.B.MODE2 = 1u;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
/* Alternative
|
||||||
|
AFIO.PCFR.B.TIM1RM = 1u;
|
||||||
|
// PC3 is T1CH1N, PC6 is T1CH1, 10MHz Output alt func, push-pull
|
||||||
|
GPIOC.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
|
||||||
|
r.B.CNF3 = 2u;
|
||||||
|
r.B.MODE3 = 1u;
|
||||||
|
r.B.CNF6 = 2u;
|
||||||
|
r.B.MODE6 = 1u;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
// Reset TIM1 to init all regs
|
||||||
|
RCC.APB2PRSTR.B.TIM1RST = SET;
|
||||||
|
RCC.APB2PRSTR.B.TIM1RST = RESET;
|
||||||
|
// CTLR1: default is up, events generated, edge align
|
||||||
|
// SMCFGR: default clk input is CK_INT
|
||||||
|
// Prescaler
|
||||||
|
TIM1.PSC.R = 0u;
|
||||||
|
// Auto Reload - sets period
|
||||||
|
TIM1.ATRLR.R = MAXPWM - 1;
|
||||||
|
|
||||||
|
TIM1.CCER.modify([](TIM1_Type::CCER_DEF & r) -> auto {
|
||||||
|
// Enable CH1N, CH1 output, positive pol
|
||||||
|
r.B.CC1NE = SET;
|
||||||
|
r.B.CC1E = SET;
|
||||||
|
/*
|
||||||
|
r.B.CC1NP = SET; // active Low
|
||||||
|
r.B.CC1P = SET;
|
||||||
|
*/
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
// CH1 Mode is output, PWM1 (CC1S = 00, OC1M = 110)
|
||||||
|
TIM1.CHCTLR1_Output.modify([](TIM1_Type::CHCTLR1_Output_DEF & r) -> auto {
|
||||||
|
r.B.OC1M = 0x6u;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
// Enable TIM1 outputs
|
||||||
|
TIM1.BDTR.modify([](TIM1_Type::BDTR_DEF & r) -> auto {
|
||||||
|
r.B.MOE = SET;
|
||||||
|
//r.B.DTG = 48u; // Dead time 1us
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reload immediately + Trigger DMA
|
||||||
|
TIM1.SWEVGR.B.UG = SET;
|
||||||
|
TIM1.DMAINTENR.B.UDE = SET;
|
||||||
|
// Enable TIM1
|
||||||
|
TIM1.CTLR1.B.CEN = SET;
|
||||||
|
}
|
||||||
|
typedef __SIZE_TYPE__ size_t;
|
||||||
|
static inline void dma1ch5_init (void * ptr) noexcept {
|
||||||
|
// Enable DMA
|
||||||
|
RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto {
|
||||||
|
r.B.SRAMEN = SET;
|
||||||
|
r.B.DMA1EN = SET;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
// DMA5 can be configured to attach to T1UP
|
||||||
|
// The system can only DMA out at ~2.2MSPS. 2MHz is stable.
|
||||||
|
DMA1.CNTR5 .R = FULL_LEN;
|
||||||
|
DMA1.MADDR5.R = reinterpret_cast<size_t>(ptr);
|
||||||
|
DMA1.PADDR5.R = reinterpret_cast<size_t>(& TIM1.CH1CVR);
|
||||||
|
NVIC.EnableIRQ (DMA1_Channel5_IRQn);
|
||||||
|
DMA1.CFGR5.modify([](DMA1_Type::CFGR5_DEF & r) -> auto {
|
||||||
|
r.B.DIR = SET; // MEM2PERIPHERAL
|
||||||
|
r.B.PL = 3u; // Highest 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 ch5
|
||||||
|
r.B.EN = SET;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
PcmDma::PcmDma() noexcept : pL(buffer), pH(buffer + HALF_LEN), src(nullptr) {
|
||||||
|
pInstance = this;
|
||||||
|
tim1pwm_init ();
|
||||||
|
dma1ch5_init (buffer);
|
||||||
|
}
|
28
common/pcmdma.h
Normal file
28
common/pcmdma.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#ifndef PCMDMA_H
|
||||||
|
#define PCMDMA_H
|
||||||
|
#include "oneway.h"
|
||||||
|
#ifdef HAVE_CONFIG
|
||||||
|
/* Umožní použít externí parametry. */
|
||||||
|
#include "pwmconfig.h"
|
||||||
|
#else
|
||||||
|
static constexpr unsigned HALF_LEN = 0x40u;
|
||||||
|
static constexpr unsigned MAXPWM = 2000u;
|
||||||
|
#endif
|
||||||
|
static constexpr unsigned FULL_LEN = 2u * HALF_LEN;
|
||||||
|
/* Používá TIM1, PWM kanál 1, DMA1 kanál 5, přerušení DMA1_Channel5_IRQHandler */
|
||||||
|
class PcmDma {
|
||||||
|
uint16_t * const pL;
|
||||||
|
uint16_t * const pH;
|
||||||
|
uint16_t buffer [FULL_LEN];
|
||||||
|
OneWay * src;
|
||||||
|
public:
|
||||||
|
explicit PcmDma () noexcept;
|
||||||
|
void attach (OneWay & s) { src = & s; }
|
||||||
|
void send (const bool b) {
|
||||||
|
if (!src) return;
|
||||||
|
if (b) src->Send (pH, HALF_LEN);
|
||||||
|
else src->Send (pL, HALF_LEN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PCMDMA_H
|
66
midi/Makefile
Normal file
66
midi/Makefile
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# ch32v003
|
||||||
|
TARGET?= ch32v003
|
||||||
|
#TARGET?= stm32f051
|
||||||
|
TOOL ?= gcc
|
||||||
|
#TOOL ?= clang
|
||||||
|
|
||||||
|
PRJ = example
|
||||||
|
|
||||||
|
VPATH = . ./$(TARGET)
|
||||||
|
BLD = ./build/
|
||||||
|
DFLAGS = -d
|
||||||
|
LFLAGS = -g
|
||||||
|
LDLIBS =
|
||||||
|
BFLAGS = --strip-unneeded
|
||||||
|
|
||||||
|
CFLAGS = -MMD -Wall -ggdb -fno-exceptions -ffunction-sections -fdata-sections
|
||||||
|
CFLAGS+= -I. -I./common -I./$(TARGET) -I/usr/include/newlib -DHAVE_CONFIG=1
|
||||||
|
DEL = rm -f
|
||||||
|
|
||||||
|
# zdrojaky
|
||||||
|
OBJS = main.o pcmdma.o
|
||||||
|
OBJS += tone.o midiplayer.o miditone.o melody.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
|
||||||
|
# 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
|
||||||
|
melody.c: ton/miditones
|
||||||
|
ton/miditones -d -s2 -t4 mid/
|
||||||
|
ton/miditones: ton/miditonesV1.6.c
|
||||||
|
gcc -Os -Wno-pointer-sign -Wno-return-type ton/miditonesV1.6.c -o ton/miditones
|
||||||
|
|
||||||
|
# vycisti
|
||||||
|
clean:
|
||||||
|
$(DEL) $(BLD)* *.lst *.bin *.elf *.map *~ miditone.c melody.c
|
||||||
|
distclean: clean
|
||||||
|
$(DEL) ton/gen ton/miditones
|
||||||
|
.PHONY: all clean distclean flash
|
16
midi/audio.h
Normal file
16
midi/audio.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef AUDIO_H
|
||||||
|
#define AUDIO_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static constexpr int AudioSampleRate = 24000;
|
||||||
|
/// Počet generátorů.
|
||||||
|
static constexpr unsigned int maxGens = 4;
|
||||||
|
/// 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
|
1
midi/ch32v003
Symbolic link
1
midi/ch32v003
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../ch32v003/
|
1
midi/common
Symbolic link
1
midi/common
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../common/
|
17
midi/main.cpp
Normal file
17
midi/main.cpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "midiplayer.h"
|
||||||
|
#include "pcmdma.h"
|
||||||
|
|
||||||
|
static MidiPlayer player;
|
||||||
|
static PcmDma pcm;
|
||||||
|
|
||||||
|
int main (void) {
|
||||||
|
pcm.attach (player);
|
||||||
|
for (;;) {
|
||||||
|
/* BUG:
|
||||||
|
* Do smyčky nejde přidat jakýkoli další kód.
|
||||||
|
* Proč, neumím vysvětlit, ale lze to izolovat.
|
||||||
|
* Je zajímavé, že ADC postavené podobně to nedělá.
|
||||||
|
* */
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
BIN
midi/mid/joy.mid
Normal file
BIN
midi/mid/joy.mid
Normal file
Binary file not shown.
BIN
midi/mid/ukoleb.mid
Normal file
BIN
midi/mid/ukoleb.mid
Normal file
Binary file not shown.
108
midi/midiplayer.cpp
Normal file
108
midi/midiplayer.cpp
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
#include "midiplayer.h"
|
||||||
|
#include "tone.h"
|
||||||
|
#include "audio.h"
|
||||||
|
#include "pcmdma.h"
|
||||||
|
//#include "filtr.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 Filtr iir;
|
||||||
|
|
||||||
|
/// Generátory tónů
|
||||||
|
static Tone gens[maxGens];
|
||||||
|
/// Generuj vzorek pro všechny tóny @return Vzorek
|
||||||
|
static inline short genSample (void) {
|
||||||
|
int res = 0;
|
||||||
|
for (unsigned int i=0; i<maxGens; i++) res += gens[i].step();
|
||||||
|
// Pro jistotu omezíme - předejdeme chrastění
|
||||||
|
if (res > maxValue) res = maxValue;
|
||||||
|
if (res < minValue) res = minValue;
|
||||||
|
return (res);
|
||||||
|
}
|
||||||
|
/// 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 ();
|
||||||
|
}
|
||||||
|
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() noexcept : OneWay() /*, but(GpioPortA, 2, GPIO_Mode_IN)*/ {
|
||||||
|
//but.setPuPd(GPIO_PuPd_UP);
|
||||||
|
index = 0;
|
||||||
|
pause = 0;
|
||||||
|
melody = scores[index++];
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
unsigned MidiPlayer::Send (uint16_t * const ptr, const unsigned len) {
|
||||||
|
// if (!but.get()) running = true;
|
||||||
|
|
||||||
|
if (!running) {
|
||||||
|
for (unsigned n=0; n<len; n++) ptr [n] = 1000;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned n=0; n<len; n++) {
|
||||||
|
const short s = nextSample();
|
||||||
|
ptr [n] = pwm_sd (s);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
void MidiPlayer::stop (void) {
|
||||||
|
//running = false;
|
||||||
|
melody = scores[index++];
|
||||||
|
if (!melody) {
|
||||||
|
index = 0;
|
||||||
|
melody = scores[index++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void MidiPlayer::ToneChange (void) {
|
||||||
|
unsigned char midt;
|
||||||
|
|
||||||
|
for (;;) { // Pro všechny tóny před pauzou
|
||||||
|
unsigned char cmd = * melody++;
|
||||||
|
if (cmd & 0x80) { // event
|
||||||
|
const unsigned geno = cmd & 0x0F;
|
||||||
|
cmd >>= 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
midi/midiplayer.h
Normal file
31
midi/midiplayer.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef DACPLAYER_H
|
||||||
|
#define DACPLAYER_H
|
||||||
|
#include "oneway.h"
|
||||||
|
#include "gpio.h"
|
||||||
|
|
||||||
|
/// Třída, která hraje čistě na pozadí.
|
||||||
|
class MidiPlayer : public OneWay {
|
||||||
|
// Veřejné metody
|
||||||
|
public:
|
||||||
|
/// Konstruktor
|
||||||
|
explicit MidiPlayer () noexcept;
|
||||||
|
unsigned Send (uint16_t * const ptr, const unsigned len) override;
|
||||||
|
//bool send (uint16_t * ptr, const int len) override;
|
||||||
|
void stop ();
|
||||||
|
protected:
|
||||||
|
// Chráněné metody
|
||||||
|
/// Obsluha tónu
|
||||||
|
void ToneChange (void);
|
||||||
|
/// Obsluha vzorku
|
||||||
|
short nextSample (void);
|
||||||
|
private:
|
||||||
|
//GpioClass but;
|
||||||
|
volatile bool running;
|
||||||
|
unsigned char const * melody;
|
||||||
|
unsigned index;
|
||||||
|
volatile int pause;
|
||||||
|
|
||||||
|
};
|
||||||
|
extern "C" const unsigned char * const scores[];
|
||||||
|
|
||||||
|
#endif // DACPLAYER_H
|
7
midi/pwmconfig.h
Normal file
7
midi/pwmconfig.h
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
static constexpr unsigned HALF_LEN = 0x80u;
|
||||||
|
static constexpr unsigned MAXPWM = 2000u;
|
||||||
|
|
||||||
|
#endif // CONFIG_H
|
1
midi/stm32f051
Symbolic link
1
midi/stm32f051
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../stm32f051/
|
76
midi/ton/gen.cpp
Normal file
76
midi/ton/gen.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
#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);
|
||||||
|
}
|
999
midi/ton/miditonesV1.6.c
Normal file
999
midi/ton/miditonesV1.6.c
Normal file
|
@ -0,0 +1,999 @@
|
||||||
|
/*********************************************************************************
|
||||||
|
*
|
||||||
|
* 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] <basefilename>
|
||||||
|
*
|
||||||
|
* The <basefilename> 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 <basefilename>.log file
|
||||||
|
*
|
||||||
|
* -lg Log output bytestream generation information to the <basefilename>.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 <basefilename>.bin, instead of a
|
||||||
|
* C-language source file with the name <basefilename>.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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
/*********** 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] <basefilename>",
|
||||||
|
" -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: <basefilename>.mid",
|
||||||
|
"output file: <basefilename>.bin or .c",
|
||||||
|
"log file: <basefilename>.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<len; ++i)
|
||||||
|
if (buf[i] != match[i]) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* announce a fatal MIDI file format error */
|
||||||
|
|
||||||
|
void midi_error (char *msg, unsigned char *bufptr) {
|
||||||
|
unsigned char *ptr;
|
||||||
|
fprintf(stderr, "---> 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; i<meta_length; ++i) {
|
||||||
|
int ch = t->trkptr[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; i<meta_length; ++i) {
|
||||||
|
int ch = t->trkptr[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<<chan) & channel_mask) { // if we're processing this channel
|
||||||
|
t->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<<chan) & channel_mask) { // if we're processing this channel
|
||||||
|
t->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, "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; i<file_count; i++) {
|
||||||
|
fprintf (outfile, "score%d, ", i);
|
||||||
|
}
|
||||||
|
fprintf (outfile, "0\n};\n");
|
||||||
|
fclose (outfile);
|
||||||
|
}
|
||||||
|
/********************* main ****************************/
|
||||||
|
|
||||||
|
int main(int argc,char *argv[]) {
|
||||||
|
int argno;
|
||||||
|
char *filebasename;
|
||||||
|
char filename[MAXPATH];
|
||||||
|
DIR * mdir; struct dirent * mdirent;
|
||||||
|
|
||||||
|
|
||||||
|
printf("MIDITONES V%s, (C) 2011 Len Shustek\n", VERSION);
|
||||||
|
printf("See the source code for license information.\n\n");
|
||||||
|
if (argc == 1) { /* no arguments */
|
||||||
|
SayUsage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* process options */
|
||||||
|
|
||||||
|
argno = HandleOptions(argc,argv);
|
||||||
|
filebasename = argv[argno];
|
||||||
|
|
||||||
|
/* Open the log file */
|
||||||
|
|
||||||
|
if (logparse || loggen) {
|
||||||
|
strncpy(filename, filebasename, MAXPATH);
|
||||||
|
strncat(filename, ".log", MAXPATH);
|
||||||
|
logfile = fopen(filename, "w");
|
||||||
|
if (!logfile) {
|
||||||
|
fprintf(stderr, "Unable to open log file %s", filename);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (directory) {
|
||||||
|
mdir = opendir (filebasename);
|
||||||
|
if (!mdir) return 1;
|
||||||
|
outfile = fopen ("melody.c", "w");
|
||||||
|
if (!outfile) return 1;
|
||||||
|
while (1) {
|
||||||
|
mdirent = readdir (mdir);
|
||||||
|
if (!mdirent) break;
|
||||||
|
int result = CreateFilename (filebasename, filename, mdirent->d_name);
|
||||||
|
if (!result) continue;
|
||||||
|
printf ("Process: \"%s\"\n", filename);
|
||||||
|
ProcessMidiFile (filename);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
fprintf (outfile, "\nconst unsigned char * scores[] = {\n");
|
||||||
|
int i;
|
||||||
|
for (i=0; i<file_count; i++) {
|
||||||
|
fprintf (outfile, "score%d, ", i);
|
||||||
|
}
|
||||||
|
fprintf (outfile, "0\n};\n");
|
||||||
|
fclose (outfile);
|
||||||
|
*/
|
||||||
|
AppendScores (outfile, file_count);
|
||||||
|
closedir (mdir);
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProcessMidiFile (filebasename);
|
||||||
|
AppendScores (outfile, 1);
|
||||||
|
//fclose (outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
printf (" Done.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
70
midi/tone.cpp
Normal file
70
midi/tone.cpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
26
midi/tone.h
Normal file
26
midi/tone.h
Normal file
|
@ -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
|
88
stm32f051/pcmdma.cpp
Normal file
88
stm32f051/pcmdma.cpp
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#include "STM32F0x1.h"
|
||||||
|
#include "CortexM0.h"
|
||||||
|
#include "gpio.h"
|
||||||
|
#include "pcmdma.h"
|
||||||
|
|
||||||
|
typedef __SIZE_TYPE__ size_t;
|
||||||
|
/* TIMER:
|
||||||
|
* fs = 8000Hz, ft = 3 * fs = 24000Hz
|
||||||
|
* reload = SystemCoreClock / ft = 48000000Hz / 24000Hz = 2000
|
||||||
|
* PINY : +PA9[AF2], -PB0[AF2] => TIM1:CH2
|
||||||
|
* DMA : TIM1_UP = 5
|
||||||
|
*/
|
||||||
|
static void Dma1Ch5Init (void * addr) {
|
||||||
|
// Configure the peripheral data register address etc
|
||||||
|
DMA1. CPAR5.R = reinterpret_cast<size_t> (& (TIM1.CCR2));
|
||||||
|
DMA1. CMAR5.R = reinterpret_cast<size_t> (addr);
|
||||||
|
DMA1.CNDTR5.R = FULL_LEN;
|
||||||
|
// Configure increment, size, interrupts and circular mode
|
||||||
|
DMA1.CCR5.modify([](auto & ccr) -> auto {
|
||||||
|
ccr.B.MINC = SET;
|
||||||
|
ccr.B.MSIZE = 1u;
|
||||||
|
ccr.B.PSIZE = 1u;
|
||||||
|
ccr.B.DIR = SET;
|
||||||
|
ccr.B.HTIE = SET; // Po půlce přerušit.
|
||||||
|
ccr.B.TCIE = SET; // Po dokončení přerušit.
|
||||||
|
ccr.B.CIRC = SET;
|
||||||
|
ccr.B.EN = RESET;
|
||||||
|
return ccr.R;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
static PcmDma * PcmDmaInstance = nullptr;
|
||||||
|
PcmDma::PcmDma() noexcept : pL(buffer), pH(buffer + HALF_LEN) {
|
||||||
|
PcmDmaInstance = this;
|
||||||
|
for (unsigned n=0; n<FULL_LEN; n++) buffer[n] = MAXPWM >> 1;
|
||||||
|
GpioClass pin1p (GpioPortA, 9, GPIO_Mode_AF);
|
||||||
|
GpioClass pin1n (GpioPortB, 0, GPIO_Mode_AF);
|
||||||
|
pin1p.setAF (2);
|
||||||
|
pin1n.setAF (2);
|
||||||
|
// 1. Enable clock peripheral
|
||||||
|
RCC.APB2ENR.B.TIM1EN = SET;
|
||||||
|
RCC.AHBENR. B.DMA1EN = SET;
|
||||||
|
// 2. Timer
|
||||||
|
TIM1.PSC.R = 0u;
|
||||||
|
TIM1.ARR.R = MAXPWM - 1;
|
||||||
|
TIM1.RCR.R = 0u;
|
||||||
|
// OC preload, CC output, Mode 6 = PWM1
|
||||||
|
TIM1.CCMR1_Output.modify([](TIM1_Type::CCMR1_Output_DEF & r) -> auto {
|
||||||
|
r.B.OC2PE = SET;
|
||||||
|
r.B.OC2M = 6u;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
// povol pin + negaci
|
||||||
|
TIM1.CCER.modify([](TIM1_Type::CCER_DEF & r) -> auto {
|
||||||
|
r.B.CC2E = SET;
|
||||||
|
r.B.CC2NE = SET;
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
// Set Output, dead time
|
||||||
|
TIM1.BDTR.modify([](TIM1_Type::BDTR_DEF & r) -> auto {
|
||||||
|
r.B.DTG = 48u; // dead: 1 us
|
||||||
|
r.B.MOE = SET; // Main output enable
|
||||||
|
//r.B.OSSR = 1u; // Off-state selection for Run mode - TODO
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
// Preload
|
||||||
|
TIM1.CR1.modify([](TIM1_Type::CR1_DEF & r) -> auto {
|
||||||
|
r.B.ARPE = SET; // TIM1_ARR register is buffered
|
||||||
|
r.B.URS = SET; // Only counter overflow/underflow generates an update DMA request
|
||||||
|
return r.R;
|
||||||
|
});
|
||||||
|
/* Update DMA request enable
|
||||||
|
* Spustíme DMA - sice budou dlouhé buffery, ale přerušení jen po 20ms */
|
||||||
|
TIM1.EGR.B.UG = SET; // Reinitialize the counter and generates an update of the registers
|
||||||
|
TIM1.DIER.B.UDE = SET; // Update DMA request enabled
|
||||||
|
Dma1Ch5Init (buffer);
|
||||||
|
// 3. NVIC
|
||||||
|
NVIC_EnableIRQ (DMA1_CH4_5_6_7_DMA2_CH3_4_5_IRQn);
|
||||||
|
TIM1.CR1.R |= 1u; // enable TIM1 (překladač bohužel bere poslední bit jako half, registr to neunese)
|
||||||
|
DMA1.CCR5.R |= 1u; // enable DMA (dtto)
|
||||||
|
}
|
||||||
|
// Přerušení od DMA
|
||||||
|
extern "C" void DMA1_CH4_5_6_7_DMA2_CH3_4_5_IRQHandler (void) {
|
||||||
|
DMA1_Type::ISR_DEF status (DMA1.ISR);
|
||||||
|
DMA1.IFCR.R = status.R; // clear flags
|
||||||
|
if (!PcmDmaInstance) return;
|
||||||
|
if (status.B.HTIF5 != RESET) PcmDmaInstance->send(false);
|
||||||
|
else if (status.B.TCIF5 != RESET) PcmDmaInstance->send(true);
|
||||||
|
}
|
Loading…
Reference in a new issue